JavaScript SEO:SPAとReactサイトのインデックス登録

Googleの2段階レンダリングキュー、CSRの落とし穴、SSR・SSG・ISRの違い、JSサイトが正しくインデックス登録されているかの確認方法を解説します。

GoogleによるJavaScriptのレンダリング:仕組み

通常のHTMLページなら、Googleの処理は1段階です。HTMLをダウンロードすれば、すぐにコンテンツを確認できます。JavaScriptのページでは3段階になります。HTMLをダウンロードし、JSバンドルを読み込み、ヘッドレスChromiumで実行して最終的なDOMを得ます。

GoogleはChromiumベースの独自ブラウザエンジンを使い、定期的に更新しています。そのため、ES2020以降の機能、dynamic import、WebAssemblyといった最新のJavaScript機能をGooglebotは理解できます。問題は構文への対応ではなく、レンダリングの遅延とクロール時のリソース制約です。

JavaScriptの3つのレンダリング方式。SSRとSSG/ISRは完成したHTMLを返します。CSRはブラウザ上でDOMを構築するため、2段階目のインデックス登録に回ります。

数字で見るレンダリング

3〜4秒

レンダリングのタイムアウト

JSが約3〜4秒以内に実行されないと、Googleは部分的にしかレンダリングされていないページをインデックスに登録します

約150ミリ秒

CSSの所要時間

読み込みに150ミリ秒を超えるリソースはレンダリングを妨げ、クロール効率を下げることがあります

2段階

インデックス登録

JSで生成したコンテンツは、HTMLのコンテンツより遅れてインデックスに入ります。数日から数週間かかることもあります

100%

エバーグリーンなGoogle

Googlebotは常に最新の安定版Chromiumに更新されるため、最新のJS機能に完全対応しています

2段階のインデックス登録:JSコンテンツの登録が遅れる理由

Googleはクロールを2つの段階に分けています。1段階目はクロールです。GooglebotがHTMLと、見つけたすべてのリンクをダウンロードします。2段階目はレンダリングです。ボットがページをレンダリングのキューに入れ、JSを実行し、最終的なDOMをインデックスに登録します。

2つの段階の間には、数時間から数週間かかることがあります。サイトの権威性、クロールバジェット、Googleのレンダリングキューの混み具合によって変わります。重要なテキスト、H1、メタタグ、リンクがJSだけで生成されている場合、インデックスへの反映は遅れます。

実践での結論: title、description、H1、リンク、構造化データなど、SEOに欠かせないコンテンツはすべて、サーバーが返すHTMLに含めます。そうすれば遅れることなく、1段階目のインデックス登録に入ります。
2段階のインデックス登録があるため、HTMLに物理的にアクセスできるにもかかわらず、公開した新しいSPAページが2〜4週間インデックスから「消える」ことがあります。

CSRはSEO最大の落とし穴

クライアントサイドレンダリング(CSR)では、サーバーは空の<div id="root"></div>だけを返し、HTMLはすべてJSバンドルの読み込み後にブラウザ上で組み立てます。React、Vue、AngularをSPAモードで使うと、デフォルトでこの動作になります。

SEOの観点では、CSRには複数の問題があります。1)レンダリングが2段階目でしか行われない。2)クローラーにブロックされたリソース(サードパーティのスクリプトや、robots.txtでブロックされたCDN)が読み込まれず、ページが不完全な状態でレンダリングされる。3) JSエラーが1つあるだけで、インデックスに登録されるページが空白になる。

html
<!-- CSR: what Google sees in the first wave -->
<html>
  <head>
    <title></title> <!-- empty -->
  </head>
  <body>
    <div id="root"></div> <!-- no content -->
    <script src="/bundle.js"></script>
  </body>
</html>
重大な間違い: <title>、<meta name="description">、<h1>をクライアントサイドJSだけで生成することです。最初のクロールでGoogleが受け取るのは空の値かデフォルト値で、ページはそれらがない状態でインデックスに登録されます。

SSR・SSG・ISRの選び方(SEOの観点から)

CSRの問題は、HTMLをサーバー側で生成すれば解決できます。主な方法は3つあり、それぞれ向いている場面が異なります。

JavaScriptの3つのレンダリング方式とSEOへの影響。

レンダリング方式の比較

方式サーバー上のHTMLSEO動的データ
CSRなし不利完全対応
SSRあり(リクエストごと)非常に良い高い
SSGあり(ビルド時)非常に良いなし(再ビルドが必要)
ISRあり(一定間隔)非常に良い再検証で対応
各方式に向いている用途:
方式向いている用途
CSRログインが必要なSPA、ダッシュボード
SSRパーソナライズしたページ、カタログ
SSGブログ、ランディングページ、ドキュメント
ISR価格カタログ、ニュース

SSR(サーバーサイドレンダリング)は、リクエストのたびにサーバーでHTMLを生成します。データは常に最新ですが、サーバーの負荷は高くなります。Next.jsのApp Routerでは、generateMetadataとasyncコンポーネントが、デフォルトでSSRになります。

SSG(静的サイト生成)は、ビルド時にHTMLを1回だけ生成し、静的ファイルとして配信します。TTFBが最も速く、SEOに最適です。弱点は、データを変更するたびに再ビルドが必要なことです。

ISR(インクリメンタル静的再生成)は、両者を組み合わせた方式です。ページは静的にビルドし、再検証期間(revalidate: 3600)が過ぎたあとの最初のリクエストで、サーバーがバックグラウンドで再生成します。Next.jsでは、ほとんどのページで標準的に使われる方法です。

typescript
// Next.js App Router — SSG + ISR
export const revalidate = 3600; // revalidate every hour

export async function generateMetadata(): Promise<Metadata> {
  return {
    title: 'Product Catalogue',
    description: 'Full range with up-to-date prices',
  };
}

// All critical content is server-rendered:
export default async function CatalogPage() {
  const products = await fetchProducts(); // server fetch
  return (
    <main>
      <h1>Product Catalogue</h1>
      {products.map(p => <ProductCard key={p.id} {...p} />)}
    </main>
  );
}

動的リンクとナビゲーション

Googlebotがリンクとして認識するのは、<a href="/url">形式のものだけです。onClick、button、data-href、window.locationで実装したリンクは、クローラーには見えません。こうしたナビゲーションからしかたどり着けないページは、インデックスに登録されない可能性があります。

  • すべてのナビゲーションリンクに<a href>を使います。 router.push()を呼ぶbutton onClickは使いません
  • ページネーションはURLパラメーター(?page=2)で実装します。 HTMLに現れていれば、GooglebotはそうしたURLをクロールします
  • SPAのルーター: Next.jsの<Link>もReact Routerの<Link>も、サーバー側で正しい<a>タグを生成します
  • ハッシュナビゲーション(#anchor)はインデックス上で別のURLになりません。単独で上位表示したいコンテンツには使わないでください
  • 動的メニュー:メニュー項目がページ読み込み後にAPIから描画される場合、Googleはレンダリングされるまで見つけられないことがあります
確認方法: Google Search ConsoleのURL検査で「Googlebotとして表示」を使うと、クローラーがページ上で見つけたリンクがわかります。動的リンクが一覧に出ていなければ、Googlebotには見えていません。

遅延読み込みと無限スクロール

loading="lazy"を指定した画像は、Googlebotも読み込みます。最近のクローラーは遅延読み込みに対応しています。ただし条件があります。SSRの時点で、画像が正しいsrc属性を持ってDOMに存在していることです。srcをブラウザ上でJavaScriptが設定する場合、クローラーには空のタグに見えます。

無限スクロールでは、意味のあるすべての項目にクローラーがたどれる経路が必要です。連続したURLと、ユーザーがボタンをクリックしなくても機能する通常のリンクを用意します。そのうえで、URL検査やクロールを使い、レンダリング後のHTMLに目的のコンテンツが含まれているか確認します。

パターンGooglebotに見えるか推奨
<img src="/img.webp" loading="lazy">見える使用してよい
<img data-src="/img.webp"> (JS sets src)見えない避ける
URLのない無限スクロール最初の画面のみページネーションを追加する
「もっと見る」ボタンの先にあるコンテンツ見えない一部をHTMLに表示する
タブ内のコンテンツ(非表示のタブ)見える(遅れて)HTMLに重複して記述する

診断:JavaScriptのレンダリングを確認する方法

診断の核心は「Googlebotは自分のページで何を見ているのか」という問いです。これに答えるツールがいくつかあります。

診断ツール

  • Google Search Console → URL検査: Googlebotがレンダリングしたあとのページのスクリーンショット、リソースの一覧、見つかったリンクを表示します。最も直接的な確認方法です。
  • GSCの「Googlebotとして表示」:実際のクローラーを動かし、レンダリング後のHTMLを返します。ソースのHTMLと比較します。
  • curlまたはwget: JSを実行しない生のHTMLを取得します。重要なメタタグやコンテンツがcurlの出力に含まれていれば、1段階目のインデックス登録に入ります。
  • Chrome DevToolsでJavaScriptを無効にする(F12 → Ctrl+Shift+P → 「Disable JavaScript」):レンダリングなしのクローラーと同じ状態でページを見る簡単な方法です。
  • レンダリングを有効にしたScreaming Frog:「Spider > Configuration > Rendering > Googlebot」を設定すると、JSをレンダリングしながらサイトをクロールできます。レンダリング後に見つかるリンクも確認できます。
SSR/SSG向けのNext.js設定例:
bash
# Check what Google sees without JS rendering:
curl -A "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" \
  https://example.com/page

# Find all title / meta description / h1 in HTML response:
curl -s https://example.com/page | grep -E '<title>|<meta name="description"|<h1'

JavaScript SEOチェックリスト

主なチェック項目

  • titleとメタディスクリプションが、JSで生成されず、サーバーが返すHTML(SSR/SSG)に含まれている
  • H1、H2と本文が、JavaScriptなしで読める
  • ナビゲーションのリンクに<a href>を使い、onClickやbuttonを使っていない
  • 構造化データ(JSON-LD)が、HTMLレスポンスの<head>内にある
  • 画像のsrcがHTMLに静的に書かれている。loading="lazy"はLCP対象ではない画像にだけ使う
  • robots.txtが、レンダリングに必要なJS/CSSファイルをブロックしていない
  • レンダリングを妨げるJSエラーがない(ページのChrome DevToolsコンソールで確認する)
  • Core Web Vitals: LCP、INP、CLSがGoogleの基準値を満たしている
  • ページネーションがJSの状態だけでなく、URLパラメーターで実装されている
  • 正規URL(<link rel="[canonical](/glossary/canonical)">)が、JSで設定されず、HTMLに記述されている

FAQ

GoogleはJavaScriptをインデックスに登録できますか?
はい、できます。GooglebotはエバーグリーンなChromiumを使い、最新のJSを実行できます。問題は対応状況ではなく遅延です。レンダリングは2段階目で行われ、HTMLのクロールから数日から数週間後になることがあります。
React/Next.jsのサイトなら、自動的に正しくインデックス登録されますか?
SSR/SSGを使ったNext.jsなら、正しく設定されていれば問題ありません。単純なReact SPA(Create React App、SSRなしのVite)はデフォルトでCSRになり、SEOには不利です。要点は、メタタグ、h1、本文をサーバー側で生成することです。
プリレンダリングやダイナミックレンダリングは必要ですか?
ダイナミックレンダリング(Googlebotとユーザーに異なるHTMLを見せる方法)は、古い手法です。Googleは許容していますが、推奨していません。正しい方法はSSRまたはSSGで、全員に同じ完全なHTMLを返します。
React.lazyとコード分割は使えますか?
はい、使えます。React.lazyとdynamic importによるコード分割はパフォーマンスを改善し、重要なコンテンツ(H1、本文)が最初のHTMLにすでに含まれていれば、SEOに悪影響はありません。必要に応じて読み込まれるコンポーネントも、Googlebotはレンダリング時に取得します。
サイトが正しくインデックス登録されているか、どう確認しますか?
Google Search ConsoleのURL検査を使います。レンダリングをリクエストし、スクリーンショットを一般ユーザーが見る画面と比較します。あわせて、JSなしのHTMLレスポンスにtitle、h1、メタディスクリプションがあるか、curlで確認します。

Check it with SpiderHead

Free and local: run the audit on your own machine.

Try it