この記事の主役は、Martin Adamsさんが公開した china-sourcing-agents.com というB2B向けサイトです。電子機器やIoTハードウェアの調達支援をするサイトで、なんと 11言語対応。しかも、OG画像の動的生成、全文検索、問い合わせフォームまで入っているのに、月額のインフラコストは $0 だそうです。
ここ、正直かなりインパクトがあります。
「無料枠で作りました」系の話は世の中にたくさんありますが、多言語 + 検索 + 画像生成 + フォーム送信まで全部乗せで無料、というのはなかなか筋がいい。しかも、ただ安いだけではなく、設計がシンプルなのがいいんですよね。複雑なCMSやバックエンドを抱えず、ファイル中心で組んでいるのが強いと思います。

記事で紹介されている構成はこんな感じです。
ざっくり言うと、
「ほぼ静的サイト。でも必要最小限のところだけ動的」
という構成です。これがとても現代的で、私はかなり好感を持ちました。
筆者が一番驚いたのがここです。
Astro 6 では、astro dev が workerd 上で動くようになったとのこと。workerd は Cloudflare Workers と同じJavaScript実行環境です。
何がうれしいかというと、ローカルで動いたものが本番でもそのまま動きやすいことです。
昔ありがちだったのが、開発中は Node.js で動いているのに、本番は Cloudflare Workers なので process.env や fs、Node streams まわりでコケるパターン。これ、地味に心を削られるんですよね。
筆者はこれを「works locally, breaks in prod 問題が減る」と表現していますが、ほんとその通りだと思います。
個人的には、開発環境と本番環境が近いことは、機能の多さよりずっと大事なことが多いです。派手さはないけど、現場ではかなり効きます。
このサイトは、
の11言語に対応しています。しかも、コンテンツは単なる翻訳ページだけでなく、
の5種類。合計で 約250個のMDXファイル があるそうです。
ここで面白いのが、Astro 6 の Content Layer API を使って、各コンテンツ種別ごとに glob loader を1つずつ定義している点です。
glob loader は、ざっくり言うと「指定したフォルダのファイルをまとめて拾う仕組み」です。つまり、en/ や ja/ のような言語別フォルダを、一つのコレクションとして扱えます。
筆者は、getLocalizedCollection(collection, locale) というヘルパー関数で、id の先頭にある言語コードを見てフィルタしています。
これにより、en/how-to-source-from-china.mdx と ja/how-to-source-from-china.mdx が、同じ種類のコンテンツとして扱われます。
これ、地味ですがかなり賢いです。
CMSを使わずにここまで整理できるのは、「ファイルで管理する」思想がちゃんと設計に落ちているからだと思います。翻訳が増えるほど、この手の設計の良し悪しが効いてくるんですよね。
Zod でスキーマを定義しているので、
タイトルは文字列、公開日は日付、タグは配列
みたいなルールも型として守れます。
人間の記憶に頼らなくて済むので、後からの修正にも強そうです。
SNSでリンクを貼ったときに出る、あのサムネイル画像。あれが OG画像 です。
この記事では、/og.png?title=...&description=... のようなエンドポイントで、ページごとの画像を動的に作っています。
使っているのは、
という流れです。
ここで重要なのが、Cloudflare Workers にはシステムフォントがないという点。
そのため、文字をそのまま描くのではなく、文字を path に変換したSVGを使うことで、フォント依存を避けています。これはかなり工夫が効いています。
ただし、ここで筆者がハマったのが WASMファイルの扱い。
resvg.wasm は 2.4MB あり、Workerスクリプトにそのままバンドルするには大きすぎる。しかも、Vite の .wasm の扱いが環境によって不安定だったそうです。
そこで取った解決策が、
WASMを public/ に置いて、実行時に fetch する
という方法。
これは実に現実的です。ローカルでも本番でも動きやすいし、サイズ問題も避けられます。
さらに、フォントファイルも 約25KBのwoff2 を Cloudflare CDN から初回取得して、モジュールスコープにキャッシュしています。
つまり、最初だけ取りに行って、あとは使い回す設計です。こういう細かい最適化、私はかなり好きです。派手ではないけど、ちゃんと効くので。
これはかなり「あるある」で、しかも面白い事故です。
筆者は favicon 用のSVGアイコンを作ったとき、<line> に stroke="#E26B1F" を使っていました。ブラウザでは問題なし。ところが、ImageMagick を使ったレンダリングでは、線が消えたそうです。
原因は librsvg の既知のバグ。
<line> の stroke 色が、特定のレンダリング環境で無視されることがあるらしいです。
解決策は、
stroke を使わず、fill ベースの <rect> に置き換える
こと。
これはかなり実践的な教訓です。
筆者は「ブラウザ外でレンダリングするSVGは fill only にする」というルールを採用したそうですが、これは私も覚えておいた方がいいと思います。
Webサイト用のSVGって「ブラウザで見えればOK」と思いがちですが、OG画像やfavicon、サーバー側の画像生成まで考えると、急に話が変わるんですよね。

全文検索って、普通は地味に大変です。
でもこのサイトでは Pagefind を使って、11言語対応の全文検索を、バックエンドなしで実現しています。
Pagefindはビルド時に静的HTMLを巡回して、dist/client/pagefind/ にバイナリインデックスを生成します。
クライアント側のJavaScriptは約15KBと軽量で、必要になったときだけ検索インデックスを読み込む仕組みです。
さらにうれしいのは、<html lang="..."> を見てくれるので、言語ごとの検索が自然に動くこと。
これ、マルチリンガルサイトではかなり大きいです。英語の中に日本語が混ざっても困るし、言語ごとの文脈で探せるのは実用的です。

ただし注意点もあります。
Pagefind は prerender = true のページしか索引できないので、動的ルートは検索対象になりません。
でも、このサイトでは問い合わせフォームは検索されなくていいので、問題なし。
ここはむしろ、必要なものだけ静的にする方針がきれいにハマっています。
このサイトは、ほぼ全部が静的生成です。
動的なエンドポイントは次の2つだけ。
POST /api/inquiries:問い合わせフォームGET /og.png:OG画像生成問い合わせフォームでは、Zod で入力チェックし、Cloudflare Turnstile で bot を検出し、さらに honeypot(人間には見えない罠フィールド)も使っています。
この三段構えのスパム対策はかなり堅いです。
しかも、問い合わせ内容はデータベースに保存せず、メールで直接受け取るだけ。
B2Bサイトとしては、かなり割り切った設計だと思います。小規模運用なら、これで十分なケースは多いはずです。

ただし、ここで一つ重要な落とし穴があります。
筆者は最初、RSSフィードを prerender = false にしていて、本番で 500 エラーになったそうです。理由は、getCollection が Workersの実行時には使えないから。
コンテンツストアは ビルド時にしか使えないので、RSSのように静的に生成すべきものは prerender = true にしないといけないわけです。
これはかなり大事な学びです。
「動的に見えるけど、実は静的でよかった」ものは多いんですよね。RSSはまさにそれ。
私はこのエピソードを読むと、静的生成と動的処理の線引きは、便利さより理解が先に必要だなと思います。
この事例のすごさは、単に「新しい技術を使った」ことではありません。
むしろ、

という、かなり堅実な設計が貫かれている点です。
特に Astro 6 + Cloudflare Workers の組み合わせは、
“静的サイトの軽さ” と “必要最低限の動的機能” の折衷案としてかなり強いと感じます。
B2Bサイトや小〜中規模のコーポレートサイトなら、こういう作り方はかなり有力ではないでしょうか。
この記事は、派手なデモというより、実戦で効いた工夫の記録として読むと面白いです。
「多言語サイトをどう管理するか」「OG画像をどう作るか」「検索をどう入れるか」「本番とローカルのズレをどう消すか」——このあたりは、どれも現場で本当に悩むポイントです。

個人的には、特に次の3つが印象的でした。
“全部を自由に動かす” より、動かす場所を絞る方が、結局は強い。
この記事は、そのことをかなり気持ちよく見せてくれる内容でした。
参考: I built a 11-language B2B site on Astro 6 + Cloudflare Workers — here's what surprised me