Building a custom blog from scratch in 2026 is a deliberate choice. Most writers just pick a hosted platform and move on. I decided to rebuild my tech blog with Astro because I wanted to own every layer of the stack and to learn patterns that actually make a static site faster, not just different. The result is a bilingual site that serves Japanese and English content, ships zero JavaScript by default, and keeps every ounce of complexity on my machine rather than the visitor’s browser.
Here are the five patterns that made it work.
Content Collections with Zod
Astro’s Content Collections do more than organize Markdown files into folders. They enforce a contract between your content and your code. I attached a Zod schema to every article collection, which means the build step validates frontmatter before a single page renders.
The schema requires a language field that accepts only two values: ja or en. There is no ambiguity about which language a reader will get. I also added a pair field that links translations together. If I publish a post about Astro in Japanese and later translate it into English, both files share the same pair ID. This makes building a language switcher trivial because the relationship is explicit in the data, not inferred from file names.
Dates arrive from Markdown frontmatter as strings, so the schema coerces them into real Date objects automatically. That eliminates string-mangling logic inside my page templates. The most important benefit, though, is the failure mode. If a file is missing a required field or uses an invalid language code, the build crashes immediately with a clear error. I fix it in my terminal instead of discovering a broken layout or a silent 404 after deployment.
Article Conversion Pipeline
I did not write every post in this new format from day one. Years of content lived on Zenn and Dev.to, each platform with its own quirks and proprietary syntax. Rather than copy-pasting and fixing by hand, I wrote a TypeScript script that converts entire batches of articles into standard Markdown.
Zenn uses custom callout syntax for tips and warnings. My script turns those into semantic HTML aside tags so they render consistently across the site. Dev.to relies on Liquid tags for embeds and special blocks. The pipeline translates those into plain Markdown links that work anywhere.
Some posts contain asides meant only for the original platform, like a disclaimer about Medium paywalls or a Zenn-specific image path. I wrap those in HTML comments so the converter can strip them out during migration. The script processes files line by line, but it respects code boundaries. When it detects a fenced code block, it skips the transformation rules entirely. Corrupting a syntax sample would undermine the whole purpose of a tech blog, so the line-by-line parser treats code blocks as untouchable zones.
Running one command now republishes years of writing without breaking a single link or callout.
Build-Time OGP Image Generation
Social sharing images are usually an afterthought. You either design them manually or install a heavy runtime service that generates cards on demand. I wanted neither. Every Open Graph image on this site is produced during the build so that visitors receive nothing more than a lightweight img tag pointing to a static PNG.
I use Satori, which takes JSX markup and renders it to SVG. The output is crisp, predictable, and easy to template. The real optimization came from font handling. A full Japanese web font can easily top five megabytes. Loading that during the build, let alone asking a browser to fetch it, would be absurd.
Instead, I use Google Fonts subsetting. The script inspects the title text for a given post and requests only the exact glyph set needed to render that string. If a headline uses forty unique Japanese characters, only those forty characters travel across the wire. The build stays fast, and the rendered image never shows broken tofu blocks because the subset is precise. Nothing is left to runtime chance.
Dark Mode via Tailwind Tokens
I refused to decorate every element with dark: utility classes. That approach scales poorly and litters your markup with noise. I redefined the color tokens themselves so the same class name resolves to different values depending on the active theme.
すべてのサーフェスとテキストカラーにCSSカスタムプロパティを使用しています。ライトモードでは --color-white は #ffffff にマッピングされます。ダークモードでは、全く同じ変数名がほぼ黒に近い値を指します。HTML側は完全にアグノスティックな状態を保てます。カードコンポーネントは、時間帯を気にすることなく bg-ui-surface や text-ui-primary を使用できます。テーマの切り替えはルートレベルで変数の定義を変更するため、インターフェース全体が即座に反応します。
このアプローチにおける唯一のリスクは、スタイルシートが読み込まれる前に、一瞬だけ明るいコンテンツが表示されてしまう(flash of light content)ことです。私はこれを、documentの head 内に記述した小さなインラインスクリプトで解決しました。このスクリプトは最初のペイント(first paint)前に実行され、localStorage とシステム設定を確認して、即座に正しいデータ属性を設定します。スクリプトがレンダリングをブロックするのはわずか数ミリ秒であるため、ユーザーがダークモードに切り替わる前に、不快な白い閃光を目にすることはありません。
アイランドアーキテクチャとZero-JS
Astroの核心的な前提は、ページはまず静的なHTMLとして始まるべきであるということです。JavaScriptは、インタラクションが真に必要になったときにのみ導入されます。私はその考えを徹底しました。
グローバルメニューとテーマ切り替えにはReactを使用せず、単一のモジュールに収まる少量のvanilla JavaScriptで処理しています。ハイドレーションのオーバーヘッドも、仮想DOMの差分計算(diffing)も、ダウンロードすべきフレームワークのランタイムも存在しません。
使用している唯一の重いライブラリは、テキストから図を描画するMermaid.jsです。グローバルにインポートする代わりに、Intersection Observerの中にラップしました。Observerが図のコンテナを監視します。ユーザーがコンテナの数百ピクセル以内にスクロールすると、スクリプトが動的にMermaidモジュールを注入して図を描画します。記事に図が含まれていない場合、そのライブラリがネットワークに触れることはありません。初期のページロードは軽量なまま保たれ、ブラウザは読者が実際に目にするものに対してのみリソースを消費します。
Build-First(ビルド優先)のマインドセット
すべてのパターンに通底する考え方はシンプルです。「ビルド時にできる作業は、そこで済ませてしまう」ということです。サイトをデプロイする前にZodでデータを検証する。リクエスト時ではなく、事前に独自のプラットフォーム構文を変換しておく。サーバーを立ち上げる代わりに、ソーシャル画像を静的ファイルとしてレンダリングしておく。クライアントごとにロジックを送り込むのではなく、トークンを通じてテーマカラーを解決する。重いJavaScriptは、ユーザーが実際に必要とするまで遅延させる。
複雑さをビルドステップへと左シフト(leftward)させることで、ランタイムの予測可能性を維持し、ペイロードを小さく保ち、メンテナンスの負担を管理可能なレベルに抑えることができます。サイトが高速なのは、単一のテクニックによるものではなく、訪問者のブラウザ内で行われることが単純に少ないからです。それこそが、2026年に静的アーキテクチャを選択することの真の恩恵なのです。
