2023年のことですが、ベルリン拠点のクライアント向けに多言語対応のeコマースサイトをリリースしました。12言語、App Router、すべてが整然としていました。3か月後、彼らのドイツ語商品ページはイギリスのSERPでランク付けされ、英語ページはミュンヘンのユーザーに配信されていました。hreflangタグは技術的には存在していました。ただ、間違った形で存在していたため、対処に恥ずかしいほどの午後を費やしてしまいました。この経験から、ドキュメントページ以上にNext.js i18nについて学べました。
これは「公式ドキュメントを要約した記事」ではありません。Seahawk Mediaで多言語サイトを構築してきた経験、繰り返し課題になること、そして2026年に実際にランキングに影響を与える具体的な判断についてお伝えします。
---
App Routerはすべてを変えました(すべてが改善されたわけではありませんが)
Pages Routerはi18nサポートが組み込まれていました。next.config.jsにブロックを追加するだけで、Next.jsはロケール検出、サブパスルーティング、Linkコンポーネントのロケール切り替えをほぼ自動的に処理していました。完璧ではありませんでしたが、意見を持つ設計だったため、落とし穴が少なかったのです。
App Router は i18n ルーティングのネイティブサポートを完全に廃止しました。next.config.js に i18n キーはありません。自動ロケール検出もありません。ルーティングロジック全体があなたの責任になり、その責任こそがほとんどのチームが静かに混乱させる場所です。
2026年の推奨アプローチは、app/ ディレクトリのルートに [locale] 動的セグメントを置くことです。app/[locale]/page.tsx のような形です。これを next-intl または react-i18next 経由の i18next と組み合わせ、Accept-Language ヘッダーを読んでそれに応じてリダイレクトするミドルウェアファイルを追加すれば、動作する基盤ができます。
ミドルウェアが誤解される理由
ミドルウェアファイルは、人々が混同する2つの別々の仕事をしています。1つ目はロケール検出:Accept-Language ヘッダー(またはクッキー、URLプリフィックス)を読んで、どのロケールを提供するか判断することです。2つ目は、URLをリダイレクトまたは書き換えて、ユーザーが何らかの隠されたロケール状態ではなく /de/produkte で終わるようにすることです。
両方ともページがレンダリングされる前に発生する必要があります。当たり前に聞こえますが、React コンテキストプロバイダーの内部でロケール検出を実行するチームを見てきました。そうすると、Googlebot に配信される初期 HTML にロケールシグナルが全くありません。ボットはページを見て、ルート URL でそれをインデックスし、突然 canonical の混乱が生じます。
ロケール解決はミドルウェアに保つこと。常に。
---
サブパス vs サブドメイン:どちらかを選んでコミットする
永遠の議論です。/en/about vs en.yoursite.com/about。ほとんどのプロジェクトについて、私はサブパスルーティングを推奨します。理由はこうです:ドメインオーソリティを集約します。.com を指すあらゆるリンクがすべてのロケールに利益をもたらします。サブドメインを使うと、Google の目には実質的に別々のサイトを運営していることになり、各サイトのオーソリティを独立して構築する必要があります。
1つの例外があります。ローカル TLD が信頼に非常に重要な市場(ドイツの .de、フランスの .fr)にいるなら、国コード TLD はサブパスとサブドメイン両方に勝り、知覚されるローカル信頼性では最強です。これは SEO 主張ではなく、コンバージョン主張です。.de vs yourbrand.com/de でランキングが同じでも信頼メトリクスが意味のある形で動いた A/B テストを見てきました。
ただし、単一のNext.jsアプリ内でccTLDセットアップを簡単に実行することはできません。そのため、プロジェクトの99%では、サブパス、1つのアプリ、1つのドメインという構成になります。
---
hreflang: あなたを欺くタグ
ここが最も被害を見かける場所です。hreflangは概念的に難しくありません。検索エンジンに「このページには他の言語での同等ページがあります、ここにあります」と伝えます。実装の現場では、私が何度も遭遇する3つの具体的な失敗パターンがあります。
失敗パターン1: タグが相互参照されていない
hreflangセット内のすべてのページは、自身を含む他のすべてのページを参照する必要があります。/en/aboutが/de/aboutを指すhreflangを持っているのに、/de/aboutが/en/aboutへのhreflangを持ち返していない場合、Googleはセット全体を無視します。ペナルティを課すのではなく、無視します。タグが存在しないかのようにです。
Seahawk は昨年、2人の開発者が異なるコンポーネント内で独立して実装し、調整しなかったため、hreflangタグの40%が相互参照されていなかったフィンテックプロジェクトを抱えていました。修正には1時間かかりました。ランキングの回復には2ヶ月かかりました。
失敗パターン2: x-defaultタグがないか、配置が間違っている
x-defaultは、特定の言語または地域をターゲットにしていないページ用です。通常はルート/または言語選択ページです。x-defaultを英語ページに配置するチームもありますが、英語が本当にサポートされていないすべてのロケールのフォールバックであれば問題ありません。ただし、サポートされていない言語のユーザーに別のページを提供する場合、x-defaultはそこを指す必要があります。
x-defaultが完全に省略されているケースを見てきました。Googleはエラーを発生させません。単にマッチしないクエリに対してどのバージョンを表示するかを独自に判断し、その判断はしばしば間違っています。
失敗パターン3:末尾スラッシュと正規URL(Canonical)の競合
hreflangtタグが https://yoursite.com/de/ueber-uns を指しているのに、同じページの正規URLタグが https://yoursite.com/de/ueber-uns/ (末尾のスラッシュに注目) を指している場合、Googleはこれらを異なるURLとして扱います。正規URLとhreflangtが異なるものを指すようになり、Search Consoleに目立ったエラーは表示されないまま、ランキングが静かに抑制されるシグナル競合を生み出してしまいます。
next.config.js で trailingSlash を設定して、どこでも一貫性を保ってください。チェックしてください。実際に自分のURLをcurlして、出力結果を読んで確認してください。
Google Search Central のhreflangtに関するドキュメントは、全文を読む価値があります。具体的で実用的な、あの種の稀な公式ドキュメントの1つです。
---
Next.js での大規模な hreflang 生成
5言語のサイトであればhreflangtタグを手作業で書くこともできます。ただし12ロケールを3,000商品ページに渡って対応させるなら、体系的なアプローチが必要です。
僕が使っているのはこの方法です:
- ロケールのリストを1回だけ定義して、単一の設定ファイルに置いてください。
locales.config.tsみたいなファイルで、['en', 'de', 'fr', 'es', 'nl'...]という配列をエクスポートする形です。 getHreflangAlternates(pathname: string)というユーティリティ関数を構築します。ロケール非依存のパス(/about)を受け取り、全ロケール向けの{ hreflang, href }オブジェクトの配列を返す関数です。- このメソッドをルートレイアウトの root
layout.tsxで呼び出し、Next.js のmetadataAPI のalternates.languagesフィールドを使って結果をインジェクトします。これは Next.js 13.3 で追加され、正しい<link rel="alternate" hreflang="...">タグが<head>に自動生成されます。 - ブログ投稿や商品ページなど動的コンテンツを持つページの場合、すべてのロケールに翻訳があるわけではないので、
supportedLocales配列をユーティリティに渡して、実際にコンテンツが存在するロケールに限定したタグを生成します。404を返すページを指すhreflangタグは、タグを設定しないよりも悪いです。
metadata API の alternates.languages アプローチは、これを処理する最もクリーンな方法です。カスタム <Head> のやり取りもなく、タグ自体に関する外部パッケージも不要です。
---
面倒にならないロケール検出
ここに UX の落とし穴があり、それは SEO にも影響を与えます。ブラウザのロケールに基づく積極的な自動リダイレクトは両方とも悪いです。
ドイツのユーザーが yoursite.com/en/article にアクセスし、ミドルウェアがユーザーに尋ねずに /de/artikel に強制リダイレクトした場合、ユーザーの意図したナビゲーションを壊してしまいます。また、リンク共有も潜在的に壊します。誰かが英語の URL を共有し、フランスのユーザーが受信者である場合、フランス語にまだ翻訳されていないコンテンツにバウンスされる可能性があります。
私のルール:検出して、提案する、強制しない。Accept-Language ヘッダーを使って初回アクセス時に合理的なデフォルトを設定し(Cookie に保存)、ユーザーが手動でオーバーライドするのを許可し、その後のすべてのアクセスでそのオーバーライドを優先します。Cookie がヘッダーより優先されます。
SEO の観点では、Googlebot は意味のある Accept-Language ヘッダーを送信しません。URL をそのままクロールします。すべてのロケール URL がリダイレクトなしで直接アクセス可能であり、サイトマップにすべてのロケールバリアントが明示的に含まれていることを確認してください。
next-intl ミドルウェアドキュメントはこの検出戦略をよくカバーしており、新しいプロジェクトを始める誰かに送る場所です。
多言語サイト向けサイトマップ
多言語サイト向けサイトマップ
小規模サイトであれば、すべてのロケールバリアントを含む単一のsitemap.xmlで十分です。大規模サイト(複数のロケール全体で数万のURL)の場合は分割します。/sitemap.xmlにサイトマップインデックスを1つ配置し、その後/sitemaps/en.xml、/sitemaps/de.xmlなど、ロケールごとに別のサイトマップファイルを配置します。
各URLエントリには、hreflangの設定をミラーリングした<xhtml:link rel="alternate">タグを含めるべきです。ページ内タグと冗長になりますが、Googlebotがまだクロールしていないページでも関連性を認識するのに役立ちます。
Next.js 14以降では、app/sitemap.tsを経由してサイトマップを動的に生成できます。戻り値の型はメタデータAPIと同じ形状のalternates.languagesをサポートしています。以前作成した同じgetHreflangAlternatesユーティリティに接続すれば、一貫性が自動的に確保されます。
具体的な数字を挙げると、8つのロケールと4,500の商品を持つプロジェクトでは、サイトマップを分割することで、GSCのクロール統計に基づいてロケールあたりの平均クロール時間が約30%削減されました。劇的ではありませんが、意味のある改善です。
---
翻訳ライブラリ:実際に使っているもの
知る価値のあるものが3つあります。next-intlはApp Routerプロジェクトのデフォルトです。サーバーコンポーネントへのファーストクラスサポートがあり、複数形を適切に処理し、APIがクリーンです。2023年半ば以降、約40プロジェクトで使用しています。
react-i18nextは実績十分で巨大なエコシステムを持っていますが、その思考モデルはクライアントコンポーネント向けに構築されており、i18nextコアを経由してサーバーコンポーネントサポートを追加するのはnext-intlのネイティブなアプローチと比べると付け足されたような感じです。
Linguiは、翻訳ワークフローがCMSではなくプロの翻訳者を中心としている場合に検討する価値があります。抽出ツールが優れています。ただし、ContentfulやSanityからコンテンツが来るほとんどのエージェンシープロジェクトでは、next-intlのほうがシンプルです。
避けるべきこと:独自に実装することです。2020年に、クライアントが既存のPHP翻訳ファイルを直接再利用することを主張したプロジェクトで一度やりました。何とか動きましたが、2年間にわたってメンテナンスすることになり、今ではnext-intlが単一のuseTranslations()呼び出しで処理するものです。
---
FAQ
Next.js App Routerは古い`next.config.js`のi18nブロックをサポートしていますか?
いいえ。i18n設定キーはPages Router特有のものです。App Routerへ移行する場合、[locale]動的セグメントとミドルウェアファイルを使用してルーティングを実装する必要があります。古い設定ブロックはApp Routerプロジェクトでは無視されます。
URLパスに`lang`を使用するべきか、サブドメインを使用するべきか?
ほとんどのプロジェクトでは、URLパスに言語を含める(/de/のようなサブパスルーティング)が正しい選択です。サブドメインはドメインオーソリティを分散させ、個別のDNS設定が必要になります。ccTLDは、ローカルの信頼シグナルが特定の市場で実際にコンバージョン率を動かしている場合だけ、複雑さに見合う価値があります。
Googlebotは、JavaScriptでレンダリングされたロケールコンテンツをどのように処理しますか?
時によっては不適切に処理します。ロケール検出がReactコンテキスト内で純粋にクライアント側で実行され、初期HTMLにロケールマーカーがない場合、Googlebotはhydration前の状態をインデックスする可能性があります。常にミドルウェアでロケールを解決し、サーバーレンダリングされたHTML内の<html>要素に正しいlang属性があることを確認してください。ブラウザのdevtoolsではなく、ソースを表示することで確認してください(devtoolsはhydration後の状態を表示します)。
サイトに言語セレクターページがない場合、正しい `x-default` hreflang値は何ですか?
プライマリロケールのホームページ(通常は /en/)を指すようにしてください。これにより、サポートしていないユーザーの言語のフォールバックとして英語であることをGoogleに通知します。これを行うことはペナルティではなく、Googleが尊重するかどうかは分からない柔らかい優先度信号です。
有料SEOツールを使わずにhreflang実装を検証できますか?
はい。Google Search ConsoleのURL検査ツールで、インデックスされているURLに対して検出されたhreflangタグが表示されます。相互参照を自動的に検証することはできませんが、代表的なページをいくつか手動で確認できます。一括検証の場合、Screaming Frog(500URLまで無料)はhreflangタブで相互参照チェックを実行します。
---
Next.jsの多言語対応を正しく実装することは、本当に地味な作業です。設定ファイル、URLの一貫性、そして2人の開発者が異なるコンポーネントで同じものを実装していないかを確認することになります。ただし、きちんと時間をかけて実装したサイトは、複数の地域でランキングが安定しており、手を抜いたサイトは、ここに書き留めた教訓を教えてくれました。
ちなみに、ベルリンのクライアントは最終的にドイツのドイツ語ページのランキングを獲得しました。完全なクロール再実行サイクルと大量のコーヒーが必要でした。
