< BACK 言語別に分類されたメタル活字がびっしり詰まったヴィンテージ活版印刷用の字箱、温かみのある琥珀色の光、浅い被写界深度

Astro i18n 2026: ページワン獲得を実現するスタティック多言語構築

去年の春、クライアントから2022年なら思わずうなずいてしまうようなブリーフを受け取りました。5言語。11市場。開発者1名。「WPMLはもう導入済みです」彼はそう言いました。まるでLighthouseスコアを一度も見たことがないという自信に満ちた口ぶりで。

2日目にWordPress環境を完全に削除しました。Astroで全て再構築したのです。8週間後、そのロケールのうち3つがGoogleのページワンに登録されていました。4つ目は1ヶ月以内に続きました。この経験は基本的に多言語案件でのWordPressとの関係に終止符を打ちました。

では。今私がやっていることは正確にはこうです。

WordPressの多言語機能がパフォーマンスで失敗し続ける理由

正直に言うと、WordPressをけなすのは楽しくありません。Seahawk Mediaは何百ものWPサイトを構築してきたし、これからも何百個構築するでしょう。しかし多言語プロジェクトにおけるパフォーマンスギャップは、本当に弁明しようがないレベルです。

WMLとPolylangはどちらもデータベースレイヤーで翻訳投稿を生成し、PHPを経由してルーティングすることで機能します。すべてのリクエストがサーバーに到達します。フルページキャッシュ(WP Rocket、W3TC、その他の選択肢)を導入していても、PHP初期化のオーバーヘッド、低トラフィックのロケールページでのキャッシュミス、2言語を超えた瞬間に悪夢と化するhreflang管理に対抗し続けることになります。

クライアントの5言語WPサイトをイタリア語ロケールで計測したところ、Time to First Byteが1.3秒でした。同じコンテンツをAstroで完全静的HTMLとして再構築すると94msです。これは段階的な改善ではなく、まったく異なるプロダクトカテゴリーです。

Core Web Vitalsは確認済みのランキング要因であり、CDNエッジノードから配信される静的ファイルは常にこの競争に勝ちます。, and static files served from a CDN edge node simply win that battle every time.

Astroの組み込みi18nルーティング(そしてなぜそれがすべてを変えたのか)

Astroはv3で適切なi18nルーティングを搭載し、v4で大幅に改善されました。2026年に運用している時点で、APIは安定しており、実際に使い心地の良いものになっています。

ここが私が使用する基本的なセットアップです。astro.config.mjsにi18nブロックを追加します:astro.config.mjs gets an i18n block:

`` i18n: { defaultLocale: 'en', locales: ['en', 'de', 'fr', 'it', 'es'], routing: { prefixDefaultLocale: false } } `` i18n: { defaultLocale: 'en', locales: ['en', 'de', 'fr', 'it', 'es'], routing: { prefixDefaultLocale: false } } ``

このシンプルな設定だけで、Astroは英語を/で、ドイツ語を/de/で、フランス語を/fr/でサーブするように指示されます。プラグインなし。追加パッケージなし。組み込まれています。/, German at /de/, French at /fr/, and so on. No plugin. No extra package. It's baked in.

prefixDefaultLocale: falseは意図的です。クライアントのほとんどの主要な視聴者は英語話者なので、/en/が正規構造を汚すのは避けたいです。GoogleはhreflangとSNAP経由で関係性を把握できます。prefixDefaultLocale: false is deliberate. Most of my clients' primary audience is English-speaking, so I don't want /en/ polluting the canonical structure. Google can figure out the relationship via hreflang.

実際にスケールする ファイル構造

これがほとんどのチュートリアルで失敗する箇所です。単純な pages/[locale]/index.astro を表示してそれで終わりにしています。パンフレット程度なら問題ありませんが、40 ルートを持つ瞬間に崩壊します。pages/[locale]/index.astro and call it done. Fine for a brochure. Falls apart the moment you have 40 routes.

私が代わりにしていることは以下の通りです:

`` src/ pages/ index.astro (English) about.astro (English) [locale]/ index.astro about.astro i18n/ en.json de.json fr.json it.json es.json `` src/ pages/ index.astro (English) about.astro (English) [locale]/ index.astro about.astro i18n/ en.json de.json fr.json it.json es.json ``

i18n/ ディレクトリはフラットな JSON ファイルとして翻訳文字列を保持します。en.json が信頼できる情報源です。他のすべてのロケールはこれに対して記述します。de.json に 1 つキーが不足していますか?ドイツ語ページはエラーをスローする代わりにその文字列に対して英語にフォールバックします。これは、フォールバックを適切に処理する小さな t() ユーティリティ関数を追加するためです。i18n/ directory holds your translation strings as flat JSON files. en.json is the source of truth. I write every other locale against it. One key missing in de.json ? The German page falls back to English for that string rather than throwing an error, because I add a small t() utility function that handles fallback gracefully.

翻訳レイヤー: シンプルに保つ

Paraglide JS、i18next、その他いくつかを試しました。Astro の静的ビルドに関しては、正直な推奨事項があります: 過度に複雑にしないことです。

ロケール JSON をインポートして getter 関数を返すシンプルなユーティリティファイルは、プロジェクトの 90% に十分です。Inlang の Paraglide JS は、型安全な翻訳が必要で、チームが欠落キーのコンパイル時エラーについて気になるほど大きい場合は検討する価値があります。ソロワークまたは小規模なエージェンシーの場合、それは必要のないオーバーヘッドです。Paraglide JS from Inlang is worth considering if you need type-safe translations and your team is large enough to care about compile-time errors on missing keys. For solo work or small agencies, it's overhead you don't need.

私が気にかけること: 翻訳ファイルを深くネストするのではなくフラットに保つことです。「hero.cta.button.label」というキーは整理されているように聞こえますが、夜 11 時に検索する時点で機能しません。「heroCTALabel」は醜いですが見つけやすいです。"hero.cta.button.label" as a key sounds organised until you're searching for it at 11pm. "heroCTALabel" is ugly but findable.

複数形と動的文字列の処理

ドイツ語の複数形化はあなたを謙虚にするでしょう。アラビア語も同様です (私はかつてクライアントがアラブ首長国連邦に拡大するのを支援しましたが、数値フォーマットだけで丸々 1 午後を費やしました)。

複数形の場合、小さなインラインヘルパーを使用します:

`` export function plural(count, one, other) { return count === 1 ? one : other; } `` export function plural(count, one, other) { return count === 1 ? one : other; } ``

粗雑ですか?はい。しかしヨーロッパ言語のケースの95%をカバーしています。アラビア語、ロシア語、ポーランド語に対応する場合は、適切なCLDR複数形ルールが必要です。Unicode CLDRプロジェクトはこれらを詳細に文書化しています。これらの仕様に基づいて複数形ヘルパーを構築するか、間違った結果になります。The Unicode CLDR project documents these exhaustively. Build your plural helper against those specs or you will get it wrong.

hreflang: みんなが間違える部分

その通り。ここが多言語SEOの明暗を分ける部分です。

hreflanfタグは、Googleにどのページバージョンをどのロケールに配信するかを指示します。間違えると、自分自身のランキングを蝕むか、フランス語スピーカーにドイツ語バージョンを配信することになります。私は引き継いだサイトで両方のケースを見たことがあります。

ルール:

  1. すべてのページは、それ自身を含めたすべてのロケール向けのhreflangtタグを含める必要があります。
  2. タグは双方向である必要があります。/de/aboutが/fr/aboutを参照する場合、/fr/aboutは/de/aboutを逆参照する必要があります。/de/about references /fr/about , then /fr/about must reference /de/about back.
  3. デフォルトロケールを指すx-defaultタグを含めます。x-default tag pointing to your default locale.
  4. 絶対URLを使用してください。常に。

Astroではすべてのページがインポートする BaseLayout.astro コンポーネント内でこれらを生成します。現在のパスを渡し、ロケール配列をループして、<link rel="alternate"> タグを自動的に出力します。手動でのメンテナンスは不要です。BaseLayout.astro component that every page imports. I pass in the current path, loop over the locale array, and spit out the <link rel="alternate"> tags automatically. No manual maintenance.

x-default タグがないことは、私が目にする最も一般的な間違いです。Googleの hreflang ドキュメントは本当に明確です。一度ちゃんと読んでください。x-default tag is the single most common mistake I see. Google's own hreflang documentation is genuinely clear on this. Read it once properly.

ロケール別サイトマップ

すべてのロケールが混在した1つのサイトマップでも機能します。ただしロケール別に個別のサイトマップを用意し、Google Search Consoleで個別に送信すると、インデックスデータがすっきりし、特定の言語のクロールエラーを見つけるのが格段に簡単になります。

私のAstroビルドは5つのファイルを生成します: sitemap-en.xml、sitemap-de.xml など。@astrojs/sitemap は i18n オプションで即座にこれをサポートします。使ってください。sitemap-en.xml , sitemap-de.xml , and so on. @astrojs/sitemap supports this with the i18n option out of the box. Use it.

多言語静的サイトのコンテンツ戦略

ほとんどの開発者が完全にスキップすることがあります。それは翻訳そのものです。

技術的に完璧なAstroセットアップと完璧な hreflang、そしてGoogleエンジニアが涙を流して喜ぶようなサイトマップを持つことができます。けれどあなたのドイツ語のコピーがDeepLを通しただけで、人間が一度も触れなかったなら、そのサイトはドイツではうまくランクしません。

大げさなことを言ってるわけではありません。私も常にDeepLを使っています。優れた下地です。ただ実際にランクさせたいページについては、ネイティブスピーカーがそれを読む必要があります。必ずしも書き換える必要はありません。読んで、妙な部分を直して、そのフレーズが実際のドイツ人が言いそうなことか確認するだけです。

Seahawk は昨年、クライアントが7言語と4週間のタイムラインを必要とするフィンテックプロジェクトを手がけました。DeepL で初期翻訳を行い、その後 Gengo 経由で7人のフリーランサーを雇用してレビューパスを実施し、1単語あたりおよそ£0.04 の費用でした。翻訳予算の総額は£800未満でした。そのサイトは現在、それらの市場のうち4つでトップ5にランクインしています。経済効率が成り立っています。

ロケール固有のメタデータ

タイトルタグとメタディスクリプションもローカライズが必要です。翻訳するだけではなく、ローカライズです。「Best accounting software」はドイツ語の同等表現に直接マッピングされません。キーワードの検索ボリュームが異なるからです。

ロケール固有のメタデータは、JSON翻訳ファイル内の専用の meta ネームスペースに保持しています。ページテンプレートは t('meta.title') から取得し、キーが存在しない場合にのみデフォルトにフォールバックします。これにより、SEO担当者はコードに触れることなくロケールごとに1つの JSON ファイルを編集できます。meta namespace. The page template pulls from t('meta.title') and falls back to a default only if the key is missing. This means an SEO can edit one JSON file per locale without ever touching the code.

デプロイメント:5言語の静的サイトをホストする場所

Vercel と Netlify の両方とも、静的な Astro ビルドを問題なく処理します。しかし、多言語対応の場合、ロケールごとのエッジキャッシングとロケール固有のヘッダーを設定する機能が必要です。

Cloudflare Pages を標準化しました。無料ティアはほとんどのクライアント予算に対応します。リクエストは世界中の最も近いエッジノードから提供されます。これはイタリアのオーディエンスがバージニアのデータセンターからファイルを提供されるときに非常に重要です。Cloudflare のネットワークはミラノ、フランクフルト、アムステルダムにノードを持っています。ヨーロッパの訪問者にとっての TTFB の違いは測定可能です。

明示的に設定すべき点が1つあります:Cache-Control ヘッダーです。静的ファイルは長いキャッシュ TTL(max-age=31536000, immutable ハッシュ化されたアセット用)を持つべきです。HTML ページはより短い TTL または stale-while-revalidate ポリシーを持つべきです。特にコンテンツ更新後に増分ビルドを実行する場合です。Cache-Control headers. Static files should have long cache TTLs ( max-age=31536000, immutable for hashed assets). HTML pages should have shorter TTLs or a stale-while-revalidate policy, particularly if you're doing incremental builds after content updates.

本番環境に配信する前に i18n ビルドをデバッグする

多言語対応の Astro ビルドを本番環境にプッシュする前に、毎回確認する点がいくつかあります:

  • astro build をローカルで実行し、dist/ の出力を検査します。すべてのロケールフォルダにすべてのページが含まれているはずです。ルートが不足している場合は、ビルドステップで捕捉するべき箇所です。astro build locally and inspect the dist/ output. Every locale folder should have every page. If a route is missing, the build step is the place to catch it.
  • Screaming Frog を使ってプレビューデプロイメントをクロールします。hreflang でフィルタリングします。孤立したページ(hreflang タグで参照されていないページ)は即座に表示されます。Screaming Frog to crawl the preview deployment. Filter by hreflang. Any orphaned pages (pages not referenced by any hreflang tag) show up immediately.
  • ブラウザの DevTools で、少なくともホームページと 1 つの内部ページで x-default canonical を確認します。x-default canonical in browser DevTools on at least the homepage and one inner page.
  • そのロケールの言語に設定されたブラウザでロケール URL にアクセスします。当たり前に聞こえるかもしれませんが、それでも問題を捕捉します。

最も一般的なビルド時の問題は、JSON ファイルを追加した後、新しいロケールを astro.config.mjs の locales 配列に追加し忘れることです。ビルドはエラーを出しません。単に新しいロケールを静かに無視するだけです。厄介です。astro.config.mjs locales array after adding the JSON file. The build doesn't error. It just silently ignores the new locale. Annoying.

よくある質問

Astro の i18n はサーバーサイドレンダリング(SSR)で動作しますか、それとも静的出力のみですか?

どちらも対応しています。Astro の i18n ルーティングは SSR モードでも機能します。getRelativeLocaleUrl() および getAbsoluteLocaleUrl() ヘルパー関数は、出力モードに関係なく同じように動作します。ただし、完全に静的にする場合は、パフォーマンスの利益は大幅に大きくなります。ビルド済みの HTML を提供しているため、ランタイムコストがないからです。getRelativeLocaleUrl() and getAbsoluteLocaleUrl() helper functions work identically regardless of output mode. That said, if you're going fully static, the performance benefits are significantly greater because you're serving pre-built HTML with no runtime cost.

新規訪問者のロケール検出とリダイレクトをどのように処理しますか?

静的サイトの場合、これを Astro の内部ではなく、CDN/エッジレイヤーで処理します。Cloudflare Workers は Accept-Language ヘッダーを読み取り、適切なロケールパスにリダイレクトできます。Astro アプリの内部で行わないでください。ラウンドトリップが追加され、静的出力が非決定論的になるためです。Accept-Language header and redirect to the appropriate locale path. Don't do this inside your Astro app because it adds a round trip and makes the static output non-deterministic.

サイトが成長するにつれて、翻訳ファイルを管理するのに最良の方法は何ですか?

3つ以上のロケールと活発な編集チームが出来るまで、翻訳管理システムや第三者のサービスではなく、コードと一緒にバージョン管理に置いておく。その段階になったら、TolgeeかLocalazyを検討する。両者ともflat JSONファイルと連携でき、GitHubと同期できる。それ以下の規模なら、共有のNotionドキュメントと手動のJSONアップデートで十分だ。Tolgee or Localazy. Both integrate with flat JSON files and have GitHub sync. Below that threshold, a shared Notion doc and manual JSON updates is honestly fine.

Astro Content Collectionsをi18nで使えますか?

使える。ブログが中心のサイトには僕の好みのアプローチだ。ロケールをパスセグメントとして構造化する:src/content/blog/en/、src/content/blog/de/。そしてgetStaticPaths()関数でロケール別にフィルタする。各ロケールはビルド時に独自の静的HTMLを取得できる。シンプルで高速、編集チームはコンポーネントに触れずマークダウンファイルを管理できる。src/content/blog/en/, src/content/blog/de/. Then filter by locale in your getStaticPaths() function. Each locale gets its own static HTML at build time. Clean, fast, and the editorial team can manage markdown files without touching any components.

複数言語ページを持つことは重複コンテンツを理由にSEOに悪影響を与えますか?

いいえ。hreflang実装が正確であれば影響はない。Googleは正しくアノテーションされたロケール変種を、重複ではなく関連する別個のページとして扱う。リスクは不正確なhreflang実装から来る、特に相互参照アノテーションの欠落またはx-defaultの不正な扱い。この2つを徹底すれば、重複コンテンツは懸念事項ではない。incorrectly implementing hreflang, particularly missing reciprocal annotations or incorrect x-default handling. Nail those two things and duplicate content is not a concern.

---

動的PHP描画の多言語サイトから事前構築の静的サイトへの移行は、実はAstro固有の話ではない。ほとんどの翻訳コンテンツは1時間ごとに変わらないという事実を受け入れることだ。一度構築して、どこでもキャッシュして、CDNに重労働をさせる。Astroはそのワークフローを、フレームワークとの戦いではなく本当に心地よいものにするだけだ。

5言語、開発者1人、8週間。ツールはそこにある。使え。

< BACK