PaPoo
cover
technews
Author
technews
世界の技術ニュースをリアルタイムでキャッチし、日本語でわかりやすく発信。AI・半導体・スタートアップから規制動向まで、グローバルテックシーンの「今」をお届けします。

Astro 6 + Cloudflare Workersで11言語対応B2Bサイトを作った話がかなり面白い

まずは要点だけ

「11言語サイトを無料で回す」は本当にできるのか?

この記事の主役は、Martin Adamsさんが公開した china-sourcing-agents.com というB2B向けサイトです。電子機器やIoTハードウェアの調達支援をするサイトで、なんと 11言語対応。しかも、​OG画像の動的生成、全文検索、問い合わせフォームまで入っているのに、月額のインフラコストは $0 だそうです。

ここ、正直かなりインパクトがあります。
「無料枠で作りました」系の話は世の中にたくさんありますが、​多言語 + 検索 + 画像生成 + フォーム送信まで全部乗せで無料、というのはなかなか筋がいい。しかも、ただ安いだけではなく、​設計がシンプルなのがいいんですよね。複雑なCMSやバックエンドを抱えず、ファイル中心で組んでいるのが強いと思います。

image_0001.png

使っている技術スタック

記事で紹介されている構成はこんな感じです。

ざっくり言うと、
​「ほぼ静的サイト。でも必要最小限のところだけ動的」​
という構成です。これがとても現代的で、私はかなり好感を持ちました。

驚きポイント1: Astro 6 の開発モードが workerd で動く

image_0003.svg

筆者が一番驚いたのがここです。
Astro 6 では、astro devworkerd 上で動くようになったとのこと。workerd は Cloudflare Workers と同じJavaScript実行環境です。

何がうれしいかというと、​ローカルで動いたものが本番でもそのまま動きやすいことです。
昔ありがちだったのが、開発中は Node.js で動いているのに、本番は Cloudflare Workers なので process.envfs、Node streams まわりでコケるパターン。これ、地味に心を削られるんですよね。

筆者はこれを「works locally, breaks in prod 問題が減る」と表現していますが、ほんとその通りだと思います。
個人的には、​開発環境と本番環境が近いことは、機能の多さよりずっと大事なことが多いです。派手さはないけど、現場ではかなり効きます。

驚きポイント2: 11言語 × 5コンテンツ種別を、1つの仕組みで回している

image_0004.svg

このサイトは、

の11言語に対応しています。しかも、コンテンツは単なる翻訳ページだけでなく、

の5種類。合計で 約250個のMDXファイル があるそうです。

image_0005.svg

ここで面白いのが、Astro 6 の Content Layer API を使って、各コンテンツ種別ごとに glob loader を1つずつ定義している点です。
glob loader は、ざっくり言うと「指定したフォルダのファイルをまとめて拾う仕組み」です。つまり、en/ja/ のような言語別フォルダを、一つのコレクションとして扱えます。

筆者は、getLocalizedCollection(collection, locale) というヘルパー関数で、id の先頭にある言語コードを見てフィルタしています。
これにより、en/how-to-source-from-china.mdxja/how-to-source-from-china.mdx が、同じ種類のコンテンツとして扱われます。

これ、地味ですがかなり賢いです。
CMSを使わずにここまで整理できるのは、​​「ファイルで管理する」思想がちゃんと設計に落ちているからだと思います。翻訳が増えるほど、この手の設計の良し悪しが効いてくるんですよね。

Zod でスキーマを定義しているので、
タイトルは文字列、公開日は日付、タグは配列
みたいなルールも型として守れます。
人間の記憶に頼らなくて済むので、後からの修正にも強そうです。

image_0006.svg

驚きポイント3: OG画像をエッジで生成するのは便利だが、意外と罠がある

SNSでリンクを貼ったときに出る、あのサムネイル画像。あれが OG画像 です。
この記事では、/og.png?title=...&description=... のようなエンドポイントで、ページごとの画像を動的に作っています。

使っているのは、

という流れです。

image_0007.svg

ここで重要なのが、​Cloudflare Workers にはシステムフォントがないという点。
そのため、文字をそのまま描くのではなく、​文字を path に変換したSVGを使うことで、フォント依存を避けています。これはかなり工夫が効いています。

ただし、ここで筆者がハマったのが WASMファイルの扱い
resvg.wasm2.4MB あり、Workerスクリプトにそのままバンドルするには大きすぎる。しかも、Vite の .wasm の扱いが環境によって不安定だったそうです。

そこで取った解決策が、
WASMを public/ に置いて、実行時に fetch する
という方法。
これは実に現実的です。ローカルでも本番でも動きやすいし、サイズ問題も避けられます。

さらに、フォントファイルも 約25KBのwoff2 を Cloudflare CDN から初回取得して、モジュールスコープにキャッシュしています。
つまり、​最初だけ取りに行って、あとは使い回す設計です。こういう細かい最適化、私はかなり好きです。派手ではないけど、ちゃんと効くので。

image_0008.svg

驚きポイント4: SVGのstrokeが、ブラウザ外だと静かに死ぬことがある

これはかなり「あるある」で、しかも面白い事故です。
筆者は favicon 用のSVGアイコンを作ったとき、<line>stroke="#E26B1F" を使っていました。ブラウザでは問題なし。ところが、ImageMagick を使ったレンダリングでは、​線が消えたそうです。

原因は librsvg の既知のバグ
<line> の stroke 色が、特定のレンダリング環境で無視されることがあるらしいです。

解決策は、
stroke を使わず、fill ベースの <rect> に置き換える
こと。

これはかなり実践的な教訓です。
筆者は「ブラウザ外でレンダリングするSVGは fill only にする」というルールを採用したそうですが、これは私も覚えておいた方がいいと思います。
Webサイト用のSVGって「ブラウザで見えればOK」と思いがちですが、OG画像やfavicon、サーバー側の画像生成まで考えると、急に話が変わるんですよね。

image_0009.png

驚きポイント5: Pagefindが思った以上に優秀

全文検索って、普通は地味に大変です。
でもこのサイトでは Pagefind を使って、​11言語対応の全文検索を、​バックエンドなしで実現しています。

Pagefindはビルド時に静的HTMLを巡回して、dist/client/pagefind/ にバイナリインデックスを生成します。
クライアント側のJavaScriptは約15KBと軽量で、必要になったときだけ検索インデックスを読み込む仕組みです。

さらにうれしいのは、<html lang="..."> を見てくれるので、​言語ごとの検索が自然に動くこと。
これ、マルチリンガルサイトではかなり大きいです。英語の中に日本語が混ざっても困るし、言語ごとの文脈で探せるのは実用的です。

image_0012.png

ただし注意点もあります。
Pagefind は prerender = true のページしか索引できないので、動的ルートは検索対象になりません。
でも、このサイトでは問い合わせフォームは検索されなくていいので、問題なし。
ここはむしろ、​必要なものだけ静的にする方針がきれいにハマっています。

驚きポイント6: 動的なのはたった2つだけ

このサイトは、ほぼ全部が静的生成です。
動的なエンドポイントは次の2つだけ。

問い合わせフォームでは、Zod で入力チェックし、Cloudflare Turnstile で bot を検出し、さらに honeypot(人間には見えない罠フィールド)も使っています。
この三段構えのスパム対策はかなり堅いです。
しかも、問い合わせ内容はデータベースに保存せず、​メールで直接受け取るだけ。
B2Bサイトとしては、かなり割り切った設計だと思います。小規模運用なら、これで十分なケースは多いはずです。

image_0014.png

ただし、ここで一つ重要な落とし穴があります。
筆者は最初、RSSフィードを prerender = false にしていて、本番で 500 エラーになったそうです。理由は、getCollectionWorkersの実行時には使えないから。
コンテンツストアは ビルド時にしか使えないので、RSSのように静的に生成すべきものは prerender = true にしないといけないわけです。

これはかなり大事な学びです。
「動的に見えるけど、実は静的でよかった」ものは多いんですよね。RSSはまさにそれ。
私はこのエピソードを読むと、​静的生成と動的処理の線引きは、便利さより理解が先に必要だなと思います。

この構成の何がすごいのか

この事例のすごさは、単に「新しい技術を使った」ことではありません。
むしろ、

image_0015.png

という、かなり堅実な設計が貫かれている点です。

特に Astro 6 + Cloudflare Workers の組み合わせは、
“静的サイトの軽さ” と “必要最低限の動的機能” の折衷案としてかなり強いと感じます。
B2Bサイトや小〜中規模のコーポレートサイトなら、こういう作り方はかなり有力ではないでしょうか。

まとめ: 地味だけど、かなり実践的な成功例

この記事は、派手なデモというより、​実戦で効いた工夫の記録として読むと面白いです。
「多言語サイトをどう管理するか」「OG画像をどう作るか」「検索をどう入れるか」「本番とローカルのズレをどう消すか」——このあたりは、どれも現場で本当に悩むポイントです。

image_0016.png

個人的には、特に次の3つが印象的でした。

  1. Astro 6 の workerd 対応
  2. Pagefind の手軽さ
  3. 静的と動的の切り分けのうまさ

“全部を自由に動かす” より、​動かす場所を絞る方が、結局は強い。
この記事は、そのことをかなり気持ちよく見せてくれる内容でした。


参考: I built a 11-language B2B site on Astro 6 + Cloudflare Workers — here's what surprised me

同じ著者の記事