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は理解できます。問題は構文への対応ではなく、レンダリングの遅延とクロール時のリソース制約です。
数字で見るレンダリング
レンダリングのタイムアウト
JSが約3〜4秒以内に実行されないと、Googleは部分的にしかレンダリングされていないページをインデックスに登録します
CSSの所要時間
読み込みに150ミリ秒を超えるリソースはレンダリングを妨げ、クロール効率を下げることがあります
インデックス登録
JSで生成したコンテンツは、HTMLのコンテンツより遅れてインデックスに入ります。数日から数週間かかることもあります
エバーグリーンなGoogle
Googlebotは常に最新の安定版Chromiumに更新されるため、最新のJS機能に完全対応しています
2段階のインデックス登録:JSコンテンツの登録が遅れる理由
Googleはクロールを2つの段階に分けています。1段階目はクロールです。GooglebotがHTMLと、見つけたすべてのリンクをダウンロードします。2段階目はレンダリングです。ボットがページをレンダリングのキューに入れ、JSを実行し、最終的なDOMをインデックスに登録します。
2つの段階の間には、数時間から数週間かかることがあります。サイトの権威性、クロールバジェット、Googleのレンダリングキューの混み具合によって変わります。重要なテキスト、H1、メタタグ、リンクがJSだけで生成されている場合、インデックスへの反映は遅れます。
CSRはSEO最大の落とし穴
クライアントサイドレンダリング(CSR)では、サーバーは空の<div id="root"></div>だけを返し、HTMLはすべてJSバンドルの読み込み後にブラウザ上で組み立てます。React、Vue、AngularをSPAモードで使うと、デフォルトでこの動作になります。
SEOの観点では、CSRには複数の問題があります。1)レンダリングが2段階目でしか行われない。2)クローラーにブロックされたリソース(サードパーティのスクリプトや、robots.txtでブロックされたCDN)が読み込まれず、ページが不完全な状態でレンダリングされる。3) JSエラーが1つあるだけで、インデックスに登録されるページが空白になる。
<!-- 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つあり、それぞれ向いている場面が異なります。
レンダリング方式の比較
| 方式 | サーバー上のHTML | SEO | 動的データ |
|---|---|---|---|
| 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では、ほとんどのページで標準的に使われる方法です。
// 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はレンダリングされるまで見つけられないことがあります
遅延読み込みと無限スクロール
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をレンダリングしながらサイトをクロールできます。レンダリング後に見つかるリンクも確認できます。
# 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をインデックスに登録できますか?
React/Next.jsのサイトなら、自動的に正しくインデックス登録されますか?
プリレンダリングやダイナミックレンダリングは必要ですか?
React.lazyとコード分割は使えますか?
サイトが正しくインデックス登録されているか、どう確認しますか?
Check it with SpiderHead
Free and local: run the audit on your own machine.



