多言語サイトのURLを ?hl=ja-JP のようなクエリパラメータで持つと、クローラは来るのにインデックスされない——自社サイトのアクセスログを1か月分集計して、それを数字で確認しました。あわせて、翻訳ページをサイトマップに全部載せた結果、クロールの一周に約85日かかる状態になったことも分かりました。
この記事は、これから多言語対応を実装する開発者・技術責任者に向けて、URL設計とサイトマップの枚数という2つの判断を、実測値つきで共有するものです。成功事例ではありません。検索表示回数はまだゼロで、その状態も含めて記録しています。
この記事でわかること
Photo by Luke Chesser on Unsplash
- 多言語ページのURLを、クエリパラメータではなくパスにすべき理由(自社の実測付き)
- サイトマップに載せるURLの枚数が、クロール頻度に対して何を意味するか
- 言語切り替えリンクを「見える形」で置くべき理由
- 実装(Next.js App Router)の要点と、後から変えると何が高くつくか
結論
多言語サイトを作るなら、次の2つを最初に決めてください。
- 言語ごとにパスを分ける(
/ja/...)。クエリパラメータ(?hl=ja)は避ける - サイトマップに載せるのは「順位を取りにいくページ」だけにする。翻訳ページを機械的に全部載せない
どちらも後から変えられますが、URL構造の変更は既存の評価に触れる作業になり、リダイレクトの管理も増えます。最初に決めておくのが安いです。
背景:7言語のサイトで何をしたか
対象は、子ども向けの英文タイピング練習サイト Typing Sprout です。当社の有志メンバーで企画・開発・運営している小規模なサイトで、UIとレッスン本文を7言語(英語・日本語・簡体中文・韓国語・スペイン語・フランス語・イタリア語)で提供しています。
公開当初、言語の切り替えはクエリパラメータで実装していました。
https://example.com/lessons/happy-cat?hl=ja-JP
サーバ側でパラメータを見て、その言語でレンダリングする方式です。実装は簡単で、ルーティングも1系統で済みます。hreflang も canonical も、この形式で正しく出力していました。
結果はこうです。
実測1:クエリパラメータのURLは、64回クロールされてインデックスはゼロ
アクセスログを集計すると、Googlebot は ?hl= 付きのURLを64回クロールしていました。到達はしていたわけです。
一方で Search Console 上、それらのURLは1本もインデックスされませんでした。表示回数もゼロのままでした。
# ログからクローラの挙動を数える(当社サーバでの実行例)
zcat -f /var/log/nginx/example.access.log* \
| grep -i googlebot | grep 'hl=' | wc -l
クローラが来ているのにインデックスされない場合、technical な不備(robots.txt、noindex、5xx)をまず疑いますが、そのいずれでもありませんでした。応答はすべて200、noindex なし、canonical は自己参照です。
残る説明は、クエリパラメータ付きURLが「すでに知っているページの別バージョン」として扱われ、正規URLに統合されたというものです。パラメータで表示が変わるURLは、並び替えやトラッキングにも使われる形式で、検索エンジンから見て「別のページ」だと判断される保証がありません。
念のため書いておくと、これは当社の1事例です。「クエリパラメータでは絶対にインデックスされない」という一般則を主張するものではありません。ただ、64回クロールされて0本という結果を見た以上、新規に選ぶ理由はないと判断しました。
実測2:パスに変えたら、多言語URLは175本クロールされた
URLをパス方式に変更しました。英語は素のパス、他言語は接頭辞です。
https://example.com/lessons/happy-cat # 英語
https://example.com/ja/lessons/happy-cat # 日本語
https://example.com/fr/lessons/happy-cat # フランス語
旧URL(?hl=)は 308 で新URLへ転送しました。
変更後、Googlebot が取得した多言語URLは実数で175本になりました(簡体中文42・フランス語34・スペイン語29・日本語29・イタリア語27・韓国語14)。クエリパラメータ時代には存在しなかった数字です。
言語ごとに別のURLである、ということがURLの形だけで伝わる——これがパス方式の実利です。hreflang は「他の言語版がここにある」と伝える手段であって、「これは別ページだ」と主張する手段ではありません。
実測3:サイトマップの枚数は、クロール一周日数で考える
ここからが、事前に想像していなかった部分です。
多言語化にあわせて、サイトマップに全言語版のURLを載せました。133本のレッスンページ × 7言語 + 固定ページで、1,022 URL です。仕様どおり、各URLに全言語の xhtml:link クラスタを添えた、正しいサイトマップでした。
サイトマップを提出した直後、Googlebot は1日531ヒットまで巡回しました。しかしその後、こうなりました。
| 日付 | Googlebot ヒット数 |
|---|---|
| 8/05 | 531 |
| 8/06 | 37 |
| 8/07 | 90 |
| 8/08 | 1 |
| 8/09 | 8 |
| 8/10 | 14 |
| 8/11 | 12 |
1日十数回です。この数字とサイトマップの枚数を並べると、意味が変わります。
1,022 URL ÷ 12 URL/日 = 全体を一周するのに約85日
公開から5週間・被リンク1本のサイトが、Googleに「1,022ページ見てください」と言っていたわけです。しかも931本は、1文だけのレッスンページの翻訳違いでした。クロールの配分は、順位を取りにいくページではなく、同じ1文の7言語版に流れていました。
そこでサイトマップを91 URL(トップ・ランディング・チャレンジ・絵本6冊・保護者向けなど × 7言語)に絞りました。
91 URL ÷ 12 URL/日 = 一周に約8日
レッスンページは削除していません。公開もインデックス可能な状態も維持し、トップページからリンクも張られています。「存在するページ」と「クロールを使ってほしいページ」を分けただけです。
判断の目安としては、次のように考えています。
| サイトの状況 | サイトマップの枚数 |
|---|---|
| 被リンクがほぼ無い・公開直後 | 数十〜百程度。主要導線だけ |
| クロールが毎日安定して数百 | 実ページ数に近い枚数でよい |
| 1ページあたりの内容が薄い(1文・1画像など) | 枚数を増やすより、まとめて厚くする |
一周日数(枚数 ÷ 1日あたりのクロール数)を出しておくと、「載せすぎ」が数字で見えます。
実測4:言語リンクは「見える形」で置く
もう一つ、実装で見落としていた点があります。
言語切り替えは、画面右上のセレクトボックス(JavaScript)で実装していました。クローラのために、同じリンクを sr-only(視覚的に非表示・スクリーンリーダー向け)の <nav> に置いていました。
<!-- 変更前:リンクは存在するが、人には見えない -->
<nav aria-label="Site language" class="sr-only">
<a href="/ja/lessons/happy-cat" hreflang="ja-JP">日本語</a>
...
</nav>
このリンクは機能していました。前述の175本のクロールは、サイトマップとこのリンクの両方から到達しています。「隠れていてリンクが辿られない」ということはありません。
ただ、隠しリンクは可視リンクより評価が低く扱われるのが一般的な理解であり、なにより利用者が言語切り替えに気づけません。ドロップダウンを開かない限り、そのページに自分の言語版があることが分からない状態でした。
いまは百科事典のように、ページ下部に全言語を並べています。現在の言語は太字のテキストで、リンクにはしません。各リンクは同じページの他言語版(/ja/lessons/happy-cat なら /fr/lessons/happy-cat)を指します。
実装の要点(Next.js App Router)
パス方式にすると、ルーティングを言語ごとに複製したくなりますが、その必要はありません。当社の実装では、リクエスト時にパスの言語セグメントを剥がし、既存のルートにリライトしています。
- 言語セグメント(
/ja/...)を検出し、内部的には/...として処理する - 言語はリクエスト単位で下流に渡す(
<html lang>・辞書・metadata がこれを見る) canonicalは各言語の自URL、hreflangは全言語 +x-default- 内部リンクは必ず言語接頭辞を保つ(英語ページに戻してしまう実装ミスが起きやすい)
このうち最後の項目は、後から直すのが面倒な割に見落としやすい部分です。リンクを1か所にまとめ、生のリンクコンポーネントを直接使わない方針にしておくと安全です。
工数としては、既存サイトの多言語化で最も時間がかかるのは翻訳文の用意であり、URL設計そのものは大きくありません。逆に、URL設計をやり直す作業は、リダイレクト・サイトマップ・hreflang・内部リンクの全部に波及します。順序として、URLを先に決めるのが安く済みます。
多言語対応は「翻訳を用意する」より「URL設計とインデックス設計」の比重が大きい仕事です。既存サイトの多言語化や、公開後にインデックスされない状態の切り分けは、お問い合わせからご相談ください。実装前の30分で決まることが多い領域です。
よくある誤解
「hreflang を書けばインデックスされる」 hreflang は言語版の対応関係を伝えるもので、インデックスを保証しません。当社の事例では、hreflang を正しく出力したクエリパラメータURLが1本もインデックスされませんでした。
「サイトマップに載せるほど有利」 枚数は要求であって、実力ではありません。クロール数が1日十数回のサイトが1,022 URLを提示すると、クロールが薄く広く配られます。
「クローラが来ていない = 技術的な問題がある」 当社の事例では、応答は全て200、robots.txt も canonical も正常でした。それでもクロール数は1日1回まで落ちています。技術的な正しさと、クロール需要は別の話です。後者は被リンクとサイトの評価で決まります。
「翻訳ページを作れば多言語の検索流入が増える」 増えるのは、そのページが検索意図に合っている場合だけです。UIだけ翻訳して本文が同じページは、内容としては同じページの複製に近くなります。この観点は、内製化の判断でも同じ構図が出ます(システム開発の内製化とは?メリット・デメリットと成功のポイント)。
FAQ:多言語サイトのURLとクロール
Q. サブドメイン(ja.example.com)とサブディレクトリ(example.com/ja/)はどちらがよいですか。
どちらでも成立します。運用の観点では、証明書・デプロイ・計測が1系統で済むサブディレクトリのほうが小規模サイトでは扱いやすいです。当社もこちらを選びました。
Q. 英語ページを /en/ にすべきですか。
既存の英語URLがすでにインデックスされているなら、素のパスのままにしてリダイレクトを増やさない判断もあります。当社は英語を素のパス、他言語を接頭辞にしました。
Q. ?hl= のURLを既に公開しています。今から変えるべきですか。
インデックスされていないなら、早いほど傷が浅いです。当社は 308(恒久リダイレクト)で新URLへ転送しました。すでに順位が付いているURLがある場合は、そのURLだけ残す判断もあります。
Q. クロールバジェットは小規模サイトでも気にすべきですか。 「バジェット」を気にするより、枚数 ÷ 1日あたりのクロール数を計算してみてください。一周に何日かかるかが出ます。数か月かかるなら、枚数が実力を超えています。
Q. サイトマップから外したページは、インデックスされなくなりますか。 外すのは「推薦」であって「拒否」ではありません。内部リンクから辿れる限り、クロールもインデックスもされ得ます。拒否したいなら noindex を使います。
Q. 効果はどのくらいで出ますか。 当社の事例では、この記事を書いている時点でまだ表示回数はゼロです。URL構造の変更から1週間、サイトマップの絞り込みからは当日です。再クロールと再評価には数週間かかるため、短期で判定しないほうがよいと考えています。
まとめ
- 多言語ページのURLは、クエリパラメータではなくパスで分ける。当社の事例では、
?hl=のURLは64回クロールされてインデックスは0本だった - パス方式に変えたあと、多言語URLは実数175本がクロールされた
- サイトマップの枚数は「枚数 ÷ 1日あたりのクロール数 = 一周日数」で評価する。1,022 URLで約85日、91 URLで約8日
- 言語リンクは可視で置く。隠しリンクでもクローラは辿るが、利用者が気づけない
- 技術的に正しくても、クロール需要が無ければ順位は付かない。そこはコードでは解けない
数字はすべて当社が運営するサイトの実測値です。なお、Googlebot を名乗る UA には偽装したスキャナも混ざるため(当社ログでも wp-login.php を叩く「Googlebot」が観測されました)、実数はここに書いた値より少ない可能性があります。集計するときは、取得URLの中身まで見ることをおすすめします。