前回の記事では、navigator.languages を使ってブラウザの言語設定を読み取り、日本語以外の設定であればトップページから /en//zh/ へ自動的に振り分ける仕組みを実装し、その内容を解説しました。今回、この仕組みを撤去しました。きっかけは、Google Search Consoleでの判定です。

観測・事実

Google Search Consoleの「ページのインデックス登録」レポートで、当サイトの英語トップページ(https://kobayaxi.com/en/)に次のステータスがついていました。

重複しています。Google により、ユーザーがマークしたページとは異なるページが正規ページとして選択されました

同ヘルプページは、このステータスの発生条件を次のように説明しています。

この URL は、あるページセットの正規ページとして指定されていますが、Google は別の URL のほうが正規ページとして適切と判断しています。

/en/のHTMLは<link rel="canonical" href="https://kobayaxi.com/en/">で自らを正規URLと宣言しているため、これは「宣言と異なるURL(日本語トップページ/である可能性が高い)をGoogleが正規URLとして採用した」という状態にあたります。

まず疑ったのは、canonical・hreflangタグ自体の実装ミスです。実際に配信されているHTMLを確認したところ、次のようになっていました。

項目/(日本語)/en/(英語)
canonicalhttps://kobayaxi.com/https://kobayaxi.com/en/
hreflang=jahttps://kobayaxi.com/https://kobayaxi.com/
hreflang=enhttps://kobayaxi.com/en/https://kobayaxi.com/en/
hreflang=zh-CNhttps://kobayaxi.com/zh/https://kobayaxi.com/zh/
hreflang=x-defaulthttps://kobayaxi.com/https://kobayaxi.com/

各ページが自己を正しくcanonical参照し、hreflangも相互に一致しています。タグの実装ミスという仮説は棄却しました。次に検討したのは、トップページに実装していた自動リダイレクト処理です。

原因・仕組みの考察

Google Search Central「How Google crawls locale-adaptive pages」は、Googlebotのクロール時の挙動について次のように述べています。

Googlebot crawls with IP addresses based outside the USA, in addition to the US-based IP addresses…the crawler sends HTTP requests without setting Accept-Language in the request header.

(Googlebotは、米国内のIPアドレスに加えて米国外のIPアドレスからもクロールを行う。……クローラーが送信するHTTPリクエストのヘッダーには`Accept-Language`が設定されない)

撤去前のリダイレクト処理は、ブラウザのnavigator.languages(優先言語の一覧。通常はAccept-Language設定に由来する)を読み取り、先頭の言語が"ja"で始まらなければ/en/または/zh/location.replace()するというものでした。しかし上記の通り、GooglebotはAccept-Languageヘッダーを送信しません。navigator.languagesがこの設定と無関係にレンダリング環境側の既定値を返す場合、Googlebotがこのスクリプトを実行した際にどちらの分岐へ進むかは、サイト側で制御も予測もできないことになります。

Googleは「Localized versions of your pages」の中で、この種の自動リダイレクトについて明確に注意を促しています。

Avoid automatically redirecting users from one language version of a site to a different language version of a site. For example, don’t redirect based on what you think the user’s language may be. These redirections could prevent users (and search engines) from viewing all the versions of your site.

(サイトのある言語版から別の言語版へ、ユーザーを自動的にリダイレクトすることは避けてください。たとえば、ユーザーの言語をこちらが推測してリダイレクトしてはいけません。こうしたリダイレクトは、ユーザー(および検索エンジン)がサイトの全バージョンを閲覧できなくなる原因になり得ます)

前回記事の執筆時点でもこの注意点は把握しており、対策として次の3点を講じていました。

  • 自動リダイレクトはトップページだけに限定する(下層ページへのリンクはそのまま表示する)
  • 全ページにhreflangを出力し、各言語版の対応関係を明示する
  • どの言語にも一致しない訪問者向けにx-defaultで既定ページを明示する

これらはいずれも「どの言語版が存在し、どう対応しているか」をGoogleに伝えるための対策であり、トップページで発生するリダイレクトそのものをなくすものではありませんでした。Googleが挙げている懸念——検索エンジンがサイトの全バージョンを閲覧できなくなる可能性——は、hreflangを整備していても、自動リダイレクトが存在する限り解消されないことになります。

なお、Google Search Consoleの表示のみから、Google内部でどのような処理を経て/en/の正規URLが置き換わったのかを検証する手段はありません。ここまでの整理は、公開されているGoogleの仕様・推奨事項と当サイトの実装を突き合わせた推定であり、断定はできない点を付記します。

解決方法

トップページ限定の自動リダイレクト処理と、それに付随する「言語切替リンクでの選択をlocalStorageに記憶し、次回以降の自動判定より優先する」処理を撤去しました。

撤去前、トップページの<head>に埋め込んでいた処理は次の通りです。

;(() => {
  try {
    if (document.referrer && new URL(document.referrer).origin === location.origin) return
    const target = (l) => (l === 'en' ? '/en/' : l === 'zh' ? '/zh/' : null)
    const stored = localStorage.getItem('preferred-lang')
    if (stored) {
      const t = target(stored)
      if (t) location.replace(t)
      return
    }
    for (const l of navigator.languages || [navigator.language]) {
      const s = (l || '').toLowerCase()
      if (s.startsWith('ja')) return
      const t = target(s.slice(0, 2))
      if (t) { location.replace(t); return }
    }
  } catch {}
})()

撤去後、トップページの<head>にこの処理は存在しません。あわせて、言語切替リンクのクリックをlocalStorageに記憶する処理と、それにのみ使っていたリンクのdata-lang-switch属性も削除しました。各言語版への案内は、ヘッダー・フッターにある通常の言語切替リンク(//en//zh/への静的なリンク)と、全ページのhreflangタグのみで行っています。

まとめ

  • Google Search Consoleの「重複しています。Googleにより、ユーザーがマークしたページとは異なるページが正規ページとして選択されました」は、canonical・hreflangタグの実装ミスがなくても発生し得る
  • Googlebotはクロール時のHTTPリクエストでAccept-Languageを送信しないため、ブラウザの言語設定に基づくリダイレクトは、Googlebotに対して意図しない挙動をとる可能性がある
  • Googleはユーザーの言語を推測した自動リダイレクトを避けるよう明確に推奨しており、hreflangの整備はこの推奨の代替にはならない
  • 対応として、トップページ限定の自動リダイレクトと関連するlocalStorageの処理を撤去し、手動の言語切替リンクとhreflangタグのみを残した

参考