Domain 19 / 225

JavaScript SEO はクロール → レンダリング → インデックスの 3 段階で動く

JavaScript SEO の要点

Google は JavaScript を 3 段階(クロール → レンダリング → インデックス)で処理する。レンダリングまで数秒〜数日待つことがあり、CSR で初期 HTML が空のページはインデックス遅延の原因。canonical / title は HTML に書くのが最も確実、SSR が推奨

なぜこれを学ぶか

JavaScript SEO は SPA / 大規模 React アプリで「インデックスされない」事故が最も多い領域。 Googlebot は JS を実行するが、レンダリング段階で待たされたり失敗したりすると、せっかくのコンテンツが SEO 評価対象にならない。

Next.js / Remix / Nuxt / SvelteKit など現代フレームワークの SEO 設計では必須知識。

学ばないと起きること

よくある事故被害
完全 CSR の SPA で初期 HTML が空レンダリング遅延でインデックスが数日〜数ヶ月遅れる
クライアント側で canonical を後から書き換える元 HTML の canonical と矛盾、Google が正規 URL を判定不能
meta description / title を JS で動的設定して初期 HTML に書かないレンダリング失敗時にスニペットが取れない
<a routerLink> のような独自属性で内部リンクを書くGooglebot が認識せず、クロール経路が途絶える
JavaScript リダイレクト(window.location)でルーティングレンダリング失敗で Google が認識できないことがある
ユーザー操作(クリック)で初めて読み込まれるコンテンツGooglebot は操作しないので評価対象外
動的に挿入する内部リンクが <a href> 形式でないリンクが認識されず、サイト内のクロール経路が壊れる

学ぶメリット

  • React / Next.js / Vue などの SPA / SSR 設計で SEO 観点を組み込める
  • インデックスされない問題を「クロール / レンダリング / インデックス」のどこで止まっているか切り分けられる
  • 商談で「JS SEO の 3 段階」を即答できる
  • URL 検査ツールで Googlebot レンダリング結果を確認できる

仕組み

Google の JavaScript 処理 3 段階

Google が JS ページを処理するパイプライン:

段階処理内容
1. クロールURL のキューから Googlebot が HTTP リクエスト、HTML を取得
2. レンダリング取得した HTML を Headless Chromium で JS 実行、最終 DOM を生成
3. インデックスレンダリング後の HTML から内容を解析、インデックスに格納

公式: 「すべての 200 OK ページがレンダリングキューに入る」(noindex を除く)

レンダリング待ちは数秒〜数日

Google のレンダリングリソースには制約があり、ページがキューに入ってから処理されるまで 数秒〜数日かかることがある(公式表現)。 完全 CSR で初期 HTML に主要コンテンツがないと、この遅延の影響を直接受ける。

Googlebot は最新 Chromium

公式: Googlebot は evergreen 版の Chromiumを使用。 最新の Web 技術はほぼ動作するが、実行できない / してくれない API もある。

不可:

  • 自動的に許可を求めるブラウザ API(位置情報 / カメラ / 通知)
  • 永続化(localStorage / sessionStorage はリクエストごとに初期化)
  • WebSocket / WebRTC / Service Worker(リクエスト間で状態保持しない)

可能だが注意:

  • 大量の JS 実行でレンダリングが遅延
  • 巨大なバンドルサイズはレンダリングキューで待たされる

クロール / リンク発見

公式: 「JavaScript で動的にリンクを挿入しても OK、ただし <a href> 形式であること」

<!-- Googlebot が認識する -->
<a href="/products/dress">商品ページ</a>

<!-- Googlebot が認識しない -->
<a routerLink="/products/dress">商品ページ</a>
<span onclick="goto('/products/dress')">商品ページ</span>
<button onclick="navigate('/products/dress')">商品ページ</button>

JS で挿入するリンクも、最終的に <a href> の形式であれば認識される。

キー概念

canonical を JS で設定する時の注意

公式: HTML で canonical を書くのが最も確実。JS で書く場合の注意:

  1. JS で canonical を変える場合、元 HTML と同じ URL を指定する
  2. 元 HTML の canonical を JS で別 URL に書き換えるのは避ける
  3. HTML に canonical を書けないなら、JS のみで設定する(HTML には書かない)

矛盾を避けるのが原則。

title / meta description も同様

JS で動的設定は可能だが、初期 HTML に書くのが安全。 SPA で URL ごとに違う title を設定する場合、SSR または事前レンダリングが推奨。

lazy-loading の正しい使い方

OK パターン:

<!-- スクロール連動 lazy-load: Googlebot が処理する -->
<img src="article.jpg" loading="lazy" alt="...">

NG パターン:

<!-- ユーザー操作(クリック)を要求: Googlebot は操作しないので無視 -->
<button onclick="loadContent()">続きを読む</button>

ユーザーアクションを必要とする lazy-load の主要コンテンツは SEO 評価対象外。 intersection observer を使ったスクロール連動 lazy-load なら OK。

Web Components(Shadow DOM)

カスタム要素を使う場合:

  • ライト DOM の中身は通常通りインデックスされる
  • Shadow DOM の中身も Google は基本的に認識する
  • スロット(<slot>)使用時は内容が正しく結合されるかテスト

URL 検査ツールでレンダリング結果を確認するのが確実。

互換性のあるコードを書く

Google は polyfill を要求しない。差分配信(differential serving)と機能検出を組み合わせるのが推奨。

if ('IntersectionObserver' in window) {
  // モダン実装
} else {
  // フォールバック
}

ポリフィルできない API もあるので、ドキュメントを確認する。

よくある誤解

よくある誤解実際のところ出典
Googlebot は JavaScript を実行できない最新 Chromium で実行する。ただしレンダリング遅延に注意JavaScript SEO の基本
完全 CSR でも問題なくインデックスされる初期 HTML が空だとレンダリング待ちでインデックス遅延同上
canonical / title は JS で動的設定が一番柔軟HTML で書くのが最も確実、JS で書き換えるのは推奨されない同上
<a routerLink> でも Google は内部リンクとして処理しない。<a href> 形式が必要リンクのベスト プラクティス
JavaScript リダイレクト(window.location)も 301 と同じ評価レンダリング失敗で Google が認識できないリスク、サーバーサイドリダイレクトが推奨リダイレクト
クリックで読み込む lazy-load も Google が拾う拾わない。ユーザーアクション要求の lazy-load は対象外lazy-loading
Googlebot は localStorage / Cookie を持続できる持続しない。リクエストごとに初期化されるJavaScript SEO の基本
URL フラグメント(#/path)で SPA ルーティングして OKNG。Google はフラグメントを基本サポートしない、History API を使う同上

実務での適用

Next.js / Remix / Nuxt の SEO 設計

公式推奨は SSR(サーバーサイドレンダリング)または静的生成(SSG)。

レンダリング方式SEO 評価
SSR(サーバーサイドレンダリング)推奨。初期 HTML に主要コンテンツが入る
SSG(静的生成)推奨。ビルド時に HTML 生成
ISR(インクリメンタル静的再生成)OK。SSG の動的更新版
CSR(クライアントサイドのみ)非推奨。レンダリング待ちでインデックス遅延

canonical の正しい設定例(Next.js)

// HTML(Server Component)で書く
export const generateMetadata = ({ params }): Metadata => ({
  alternates: {
    canonical: `https://example.com/products/${params.slug}`,
  },
});

JS で動的に書き換えるのは避ける。

内部リンクは必ず <a href>

// OK(Next.js Link コンポーネント)
import Link from "next/link";
<Link href="/products/dress">商品ページ</Link>

// 内部実装が <a href> なので Google が認識する

// NG(onClick だけのナビゲーション)
<button onClick={() => router.push("/products/dress")}>商品ページ</button>

URL 検査ツールでレンダリング確認

Search Console の URL 検査で:

  1. URL を入力 → ライブテスト
  2. 「レンダリング済みの HTML」を確認
  3. 主要コンテンツが入っているか確認
  4. 内部リンクが <a href> で出力されているか確認

これで Googlebot が見ている内容を確認できる。

トラブル別の対処

症状確認すべきこと
公開したページがいつまでもインデックスされないURL 検査でレンダリング結果確認、初期 HTML が空でないか
canonical が正しく認識されていないHTML と JS で矛盾していないか、URL 検査で実際の認識結果確認
内部リンクが Google に発見されない<a routerLink> などカスタム属性を使っていないか
SPA のページが部分的にしかインデックスされないHistory API でルーティングしているか、各ルートで個別 URL になっているか

公式ソース

自己テスト

Q1. Googlebot は JavaScript を実行できるか?

実行できる。最新 Chromium(evergreen)を使用。 ただしレンダリングまで数秒〜数日待つことがあるので、完全 CSR は SEO 上不利

Q2. Google の JavaScript ページ処理 3 段階は?

クロール → レンダリング → インデックス。 クロールで HTML 取得、レンダリングで JS 実行、インデックスで内容解析・格納

Q3. canonical を JS で設定するのは推奨されるか?

HTML で書くのが最も確実。 JS で書き換える場合は元 HTML と矛盾しないこと。HTML に書けないなら JS のみで設定(HTML には書かない)

Q4. a routerLink= の独自属性を Google は内部リンクとして認識するか?

しない。<a href> 形式である必要がある。 JS で動的に挿入する場合も最終的に <a href> の形になっていれば OK

Q5. クリックで初めて読み込まれる lazy-load コンテンツは Google に拾われるか?

拾われない。ユーザーアクション(クリック / タップ / スワイプ)を必要とする lazy-load は SEO 評価対象外。 スクロール連動 lazy-load なら OK

Q6. SPA で URL フラグメント(`#/path`)でルーティングしてよいか?

NG。Google は基本的にフラグメントをサポートしない。 History API を使ったクリーン URL ルーティングが必須

Q7. JavaScript リダイレクト(window.location)の SEO 評価は?

Google は実行して認識するが、レンダリング失敗で気づかれないリスクがある。 サーバーサイドリダイレクト(301)が推奨

Q8. SPA で SEO に強い構成は?

SSR(サーバーサイドレンダリング)または SSG(静的生成)。 初期 HTML に主要コンテンツが入る形が SEO 上有利。 完全 CSR はレンダリング遅延の影響を直接受ける

これらの内容を採点付きで挑戦したい場合は、本ドメインのプロ試験で 5 問形式で確認できる。