シングルページアプリケーションのSEO:SPAが上位表示されない理由、SSR・静的生成・プリレンダリングによる解決策、AIクローラーにアプリを読ませる方法を解説します。

要点
シングルページアプリケーション(SPA)は、1つのHTMLシェルを読み込み、すべての画面をブラウザ上のJavaScriptで組み立てます。GoogleはJavaScriptをレンダリングできますが、そのレンダリングは遅れがちで不安定です。さらに、ChatGPT、Claude、Perplexityを支えるクローラーはJavaScriptをまったく実行しません。上位表示され、引用されるためには、SPAの各画面に固有のURL、hrefを持つ本物のリンク、固有のタイトルとディスクリプション、正しいステータスコードが必要です。そして何より、サーバーサイドレンダリング、静的生成、プリレンダリングのいずれかによって、メインコンテンツをサーバーが送信するHTMLに含める必要があります。
さらに詳しく知るには、こちらに質問
シングルページアプリケーションのSEOとは、React、Vue、Angular、Svelteで構築したJavaScriptアプリを、従来型のWebサイトと同じくらいクロールしやすくするための一連の手法です。この問題は広く知られています。GoogleはJavaScript SEOの基本で、JavaScriptページを3つのフェーズ(クロール、レンダリング、インデックス登録)で処理すると説明しており、SPAの弱点はすべてこの流れのどこかにあります。このガイドはSPAに焦点を当てています。JavaScriptサイト全般については、JavaScript SEOのガイドをご覧ください。
従来のWebサイトは、URLごとに完全なHTMLドキュメントを返します。SPAはどのURLに対してもほぼ同じドキュメントを返します。空の<div id="root">、いくつかのmetaタグ、そして大きなJavaScriptバンドルです。ブラウザがバンドルをダウンロードし、APIを呼び出してから、ページを描画します。訪問者がリンクをクリックすると、JavaScriptがページを再読み込みせずにコンテンツを入れ替えます。
高速なスマートフォンを使う人にとっては、一瞬で表示されるように感じます。しかしクローラーにとって、最初のレスポンスは空のシェルです。見出しも、テキストも、リンクもありません。すべては、ボットがJavaScriptを実行するかどうか、そしていつ実行するかにかかっています。この1つの事実が、SPAのSEO問題のほとんどを説明します。
/#/pricing)でルーティングしますが、#以降の部分はサーバーに送信されないため、すべての画面が1つのURLにまとまってしまいます。<title>が1つしかなく、ルーターが更新しなければ全ページで共有されます。hrefのないリンク:ボタンやonClickハンドラーはユーザーを遷移させますが、クローラーがたどれるものは何も残しません。Googlebotはまず生のHTMLを取得し、そこにあるリンクと指示を読み取ります。200ステータスを返したページは、ヘッドレスChromiumであるWeb Rendering Serviceのキューに入れられます。Googleによると、ページは「このキューに数秒間とどまることもありますが、それ以上かかる場合もあります」。レンダリング後、生成されたHTMLがコンテンツとリンクのために再度解析され、インデックスに登録されます。
Googleのドキュメントのうち、SPAにとって重要な点は4つです。
hrefを持つ<a>要素だけが確実なリンクです。JavaScriptで挿入されたリンクも、この形式に従っていれば問題ありません。#ルートではなくHistory API(pushState)を使うよう求めています。旧来のAJAXクロールスキーム(#!のURL)は2015年10月に非推奨となりました。noindexが優先されます。Googleはnoindexを見つけるとレンダリングを省略することがあるため、後からJavaScriptで削除しても効果はありません。実務上の結論はこうです。GoogleはクライアントサイドレンダリングのSPAもインデックスできますが、すべてのページに2つ目のステップ、遅延、障害点が加わります。レンダリング中のタイムアウト、ブロックされたAPI呼び出し、JavaScriptエラーがあれば、インデックスされるバージョンは空になります。Googleに何が見えているかはSearch ConsoleのURL検査ツールで確認し、ページがインデックスされないままならGoogleのインデックス登録の仕組みに関するガイドをお読みください。
ほとんど読みません。VercelとMERJは、Vercelのネットワークのトラフィックをもとに2024年12月に公開した調査で、「主要なAIクローラーはいずれも、現時点でJavaScriptをレンダリングしていない」ことを明らかにしました。GPTBot、OAI-SearchBot、ChatGPT-User(OpenAI)、ClaudeBot(Anthropic)、PerplexityBot、Meta-ExternalAgent、Bytespiderは、いずれも生のHTMLしか読みません。JavaScriptファイルをダウンロードはしますが(ChatGPTのリクエストの約11.5%、Claudeの約23.8%)、実行はしません。例外は、Googleのレンダリング基盤を利用するGeminiと、ブラウザベースのクローラーを使うAppleBotです。
つまりクライアントサイドレンダリングのSPAでは、ChatGPT、Claude、Perplexityにデータを供給するAIクローラーには空のページしか見えません。あなたの製品がGoogleで上位表示されていても、AIの回答にはまったく現れないことがあります。アシスタントがその内容を一語も読んでいないからです。SNSやメッセージアプリのリンクプレビュー用ボットの多くも同様で、生のHTMLにあるOpen Graphタグを読み取ります。コンテンツがHTMLに含まれたら、robots.txtで必要なAIクローラーを許可しているかも確認しましょう。
解決策は、最初のレスポンスで本物のHTMLを送ることです。Googleはダイナミックレンダリングのドキュメントで、ダイナミックレンダリング(プリレンダリングしたバージョンをボットだけに配信する方法)を「回避策であり、推奨される解決策ではありません」としており、代わりにサーバーサイドレンダリング、静的レンダリング、ハイドレーションを勧めています。以下の定義は、web.devのリファレンス記事Rendering on the Webに基づいています。
| 戦略 | 仕組み | 最初のHTMLにコンテンツがあるか | 適している用途 | 制約 |
|---|---|---|---|---|
| クライアントサイドレンダリング(CSR) | ブラウザがJavaScriptとAPIからページを組み立てる | なし | ダッシュボードやログインが必要なページ | Googleでのインデックスが遅れ、ほとんどのAIクローラーからは見えない |
| サーバーサイドレンダリング(SSR) | リクエストごとにサーバーがHTMLをレンダリングし、その後JavaScriptがハイドレーションする | あり | カタログ、一覧ページ、頻繁に変わるコンテンツやパーソナライズされたコンテンツ | サーバーコストがかかり、キャッシュしない場合は応答が遅くなる |
| 静的生成(SSG) | ビルド時にURLごとにHTMLを生成し、CDNから配信する | あり | マーケティングページ、ブログ、ドキュメント、ランディングページ | コンテンツ変更時に再ビルドが必要(増分ビルドの選択肢あり) |
| ビルド時のプリレンダリング | ヘッドレスブラウザが各ルートの初期状態を静的HTMLとして取得する | あり | 公開ルートの数が限られた既存のSPA | データはビルド時点で固定され、ルートを列挙する必要がある |
| ダイナミックレンダリング | ボットにはプリレンダリングしたスナップショット、ユーザーにはSPAを配信する | あり(リストにあるボットのみ) | レガシーアプリの一時的な対処 | 公式には回避策、保守コストがかかり、ボットに異なるコンテンツを配信するリスクがある |
最近のフレームワークの多くは、ルートごとに戦略を組み合わせられます。マーケティングページは静的に、カタログはサーバーでレンダリングし、ログイン後のアプリはクライアントでレンダリングする、といった具合です。このハイブリッド構成なら、検索流入がある場所ではクローラーに完全なHTMLを渡し、SPAの体験が価値を生む場所ではそれを維持できます。
| ライブラリ | 初回読み込みでHTMLを出すためのフレームワークまたは機能 |
|---|---|
| React | Next.js(サーバーコンポーネント、SSR、静的生成)、フレームワークモードのReact Router、Gatsby |
| Vue | Nuxt(デフォルトでユニバーサルレンダリング、静的生成にも対応) |
| Angular | Angular SSR(サーバーレンダリングとビルド時のプリレンダリング) |
| Svelte | SvelteKit(デフォルトでサーバーレンダリング、ルート単位のプリレンダリング) |
| いずれでも | Astro(デフォルトで静的HTML、インタラクティブなアイランドは必要な場所でのみ読み込み) |
AIによるWebサイトビルダーで生成されたサイトには特に注意が必要です。その多くは、クライアントサイドレンダリングのReactアプリを出力します。こうしたツールでサイトを作った場合は、GoogleやChatGPTが読めると決めつける前に、LovableのSEOとGEOに関する解説をご覧ください。
History APIを使い(/pricingとし、/#/pricingは使わない)、1つのコンテンツに1つのURLを割り当て、訪問者が直接アクセスしてもアプリ内の遷移で来ても同じURLにします。どのURLも新しいタブで開いたときに正しいコンテンツを読み込む必要があります。これこそがクローラーの行うテストです。
インデックス対象のページへ移動するナビゲーション要素は、すべて<a href="/path">にすべきです。フレームワークのリンクコンポーネント(Link、NuxtLink、routerLink)はこのマークアップを出力しますが、divやbutton要素に付けた独自のクリックハンドラーは出力しません。適切なリンクはサイト構造も伝えます。リンクされていないページは、クローラーにほとんど見つけてもらえません。
各ルートには固有のタイトル、メタディスクリプション、canonical、Open Graphタグが必要で、可能な限りサーバー側で生成します。設定すべき項目はmetaタグのページにまとめています。canonicalタグは1つだけにしてください。GoogleはHTML内で設定することを推奨しており、JavaScriptで変更する場合も、元の指定と矛盾してはいけません。
存在しない商品や削除された記事が、エラーメッセージ付きで200を返してはいけません。SSRなら、サーバーから本物の404または410を返します。純粋なSPAでは、Googleは2つの代替策を認めています。404を返すURLへJavaScriptでリダイレクトするか、エラー画面にnoindexのrobots metaタグを追加するかです。
見出し、本文、価格、よくある質問、JSON-LDの構造化データは、クリックやスクロールの後に読み込むのではなく、サーバーのレスポンスに含めるべきです。必要に応じてレンダリングされるタブの中や、「もっと見る」ボタンの後ろにあるコンテンツは、見落とされることがよくあります。
ルートごとにバンドルを分割し、最初の画面に不要なものは遅延させ、ハイドレーション中にページ全体を再レンダリングしないようにします。なお、Chromeのフィールドデータ(CrUX)は、アプリ内遷移の指標を訪問者が最初に読み込んだURLに割り当てるため、主要なテンプレートは個別にテストしてください。
主要なテンプレートごとに、3つのバージョンを比較します。生のHTML(ソースを表示するか、コマンドラインツールでページを取得)、GoogleがレンダリングしたHTML(URL検査から「クロール済みのページを表示」)、そしてJavaScriptを無効にしたページです。メインのテキストが2つ目のバージョンにしか現れないなら、AIクローラーには見えていません。無料のAI向けクローラビリティ監査とインデックス可能性チェッカーで、任意のURLに対してこれらのチェックを実行できます。
| 症状 | 考えられる原因 | 対処法 |
|---|---|---|
| Search Consoleで多くのページが「クロール済み - インデックス未登録」と表示される | レンダリング前のHTMLが空、またはほぼ同一 | 公開ルートにSSR、SSG、またはプリレンダリング |
site:検索の結果がすべて同じタイトルになる | ルートごとにタイトルが更新されていない | サーバー側でのルート単位のhead管理 |
| トップページしかインデックスされていない | ハッシュルーティング、またはhrefのないリンク | History APIと本物の<a>リンク |
| エラーページがインデックスに表示される | ソフト404(どこでもステータス200) | サーバーからの404、404のURLへのJavaScriptリダイレクト、またはnoindex |
| Googleでは上位表示されるが、ChatGPTやPerplexityに引用されない | コンテンツがJavaScriptの実行後にしか見えない | 最初のHTMLレスポンスにコンテンツを含める |
| Googleのレンダリング結果に古いコンテンツが表示される | キャッシュされたJavaScriptまたはCSSファイル | フィンガープリント付きのファイル名 |
長年、クライアントサイドレンダリングは管理可能なリスクでした。Googleが最終的にはJavaScriptをレンダリングしていたからです。しかし今では情報探索の一部がChatGPT、Claude、Gemini、Perplexityで行われており、そのコストは変わりました。JavaScriptの実行後にしか存在しないページは、ほとんどのアシスタントが読める情報源から抜け落ちてしまいます。生のHTMLにおけるクローラビリティは、生成エンジン最適化の第一条件になりました。
HTMLが完全になれば、あとは通常のルールに従います。1つの質問に明確に答え、出典のある事実と構造化データを備えたページこそ、アシスタントが引用するページです。SorankのGEO最適化機能はそうしたコンテンツの執筆と公開を支援し、AIメンション追跡は、レンダリングの修正後にChatGPT、Claude、Geminiがあなたのブランドを引用しているかどうかを示します。
シングルページアプリケーションのSEOの鉄則は、1文にまとめられます。GoogleにランクづけさせたいものやAIに引用させたいものは、すべてサーバーが送るHTMLに含めなければならない。SPAはログイン後の体験に使い、公開ページはサーバー上またはビルド時にレンダリングし、リリースのたびに、その前後でボットが受け取る内容をテストしましょう。
シングルページアプリケーションがGoogleからペナルティを受けることはありませんが、標準的な構成(1つのHTMLシェルによるクライアントサイドレンダリング)では、インデックス登録が遅く、不安定になります。ほとんどのAIクローラーはJavaScriptを実行しないため、空のページしか見えません。公開ルートでサーバーサイドレンダリング、静的生成、プリレンダリングを使えば、SPAも従来のWebサイトと同じように上位表示できます。
React自体は中立です。重要なのはHTMLがどこで生成されるかです。ブラウザだけでレンダリングするReactアプリにはSPA特有の問題がありますが、同じコンポーネントをNext.jsやフレームワークモードのReact Routerでサーバー上でレンダリングすれば、クローラーに完全なHTMLを届けられます。インデックスさせたいページには、サーバーレンダリングまたは静的レンダリングに対応したフレームワークを選びましょう。
一時的な手段としてのみ使ってください。Googleはダイナミックレンダリングを回避策と位置づけ、推奨される解決策とはしていません。ボット用に別のバージョンを保守すると、コストとリスクが増えるためです。サーバーサイドレンダリング、静的生成、ビルド時のプリレンダリングなら、すべての訪問者に同じHTMLを届けられ、長期的な解決策になります。
先行導入企業のご紹介
