SPAはSEOに弱いか
Next.js・React Router
Next.js や React Router でフロントエンドを組んだあと、「このページソースの出し方は SEO 上問題ないか」という不安を持つことがあります。多くの場合、論点は Next.js や React Router というフレームワーク自体ではなく、そのフレームワークをどのレンダリング方式で使っているかです。
Next.js・React Router のどちらも、SSR(サーバーサイドレンダリング)を使っていれば、最初に届く HTML にすでに title・meta・h1・本文が入っています。この状態であれば、SEO 上のハンデはほぼありません。弱くなるのは、データ取得も描画もすべてブラウザ側の JavaScript に任せるCSR(クライアントサイドレンダリング)だけの構成のときです。まず自分のサイトがどちらの構成かを切り分けることが、最初の一歩になります。
01.結論:弱いのは「CSRだけのSPA」
「Next.js は SEO に強い」「React の SPA は SEO に弱い」という言い方は、どちらも半分だけ正しく、半分は不正確です。実際に効いてくるのはフレームワークの名前ではなく、ページの HTML がいつ・どこで組み立てられるかという一点です。
SPA は、最初に1枚の HTML を読み込んだあとは、リンクをクリックしてもサーバーへ新しい HTML ドキュメントをリクエストせず、JavaScript が DOM を書き換え、History API で URL だけを差し替えてページ遷移したように見せるアーキテクチャです。ページ全体のフルリロードが発生しない、という遷移方式の話であり、最初の1枚の HTML が CSR で作られるか SSR で作られるかとは、別の軸の話です。対義語の MPA(マルチページアプリケーション)は、リンクをクリックするたびにサーバーへ新しい HTML をリクエストし、画面全体を読み込み直す従来型の構成を指します。
Next.js も React Router 7 も、最初はサーバー(またはビルド時)が作った HTML をそのまま表示しつつ、裏側で JavaScript が読み込まれて画面を操作できる状態に切り替わります。この「すでに表示されている HTML に JavaScript を接続し、クリックなどの操作に反応できるようにする処理」をhydration(ハイドレーション)と呼びます。hydration が終わったあとのページ遷移はクライアントサイドルーティングで行われ、リンクをクリックしてもフルリロードは発生しません。これは定義上そのまま SPA です。SSR を使っていても SPA でなくなるわけではありません。SEO への影響を決めるのは「SPA かどうか」ではなく「最初の1枚の HTML がどこで作られるか」という別の軸です。
その「最初の1枚の HTML がどこで作られるか」には、大きく3つのやり方があります。CSR(クライアントサイドレンダリング)は、ブラウザ(クライアント)が JavaScript を実行してその場で HTML の中身を組み立てるやり方です。SSR(サーバーサイドレンダリング)は、サーバーがリクエストのたびに HTML の中身を組み立ててから返すやり方です。SSG(静的サイト生成)は、サイトを公開する前のビルド時に、あらかじめ HTML の中身を作っておくやり方です。
上の図を、今度はクライアント(ブラウザ)とサーバーの間でどんなリクエスト・レスポンスが行き来しているかという角度から見ると、次のようになります。
CSR
本文を組み立てるのはブラウザ(クライアント)側。リクエストは空に近いHTMLをリクエスト、レスポンスは空に近いHTML+JSファイルが届く。ブラウザがJSを実行し、その場で本文を組み立てる。組み立て完了までは本文が存在しない。
SSR
本文を組み立てるのはサーバー側。リクエストはページをリクエスト、レスポンスは本文入りのHTMLが届く。サーバーがリクエストのたびに本文を組み立てて返す。ブラウザは届いたHTMLをそのまま表示する。
SSG
本文を組み立てるのはサーバー側。リクエストはページをリクエスト、レスポンスは本文入りのHTMLが届く(公開前に用意済み)。本文の組み立てはビルド時(公開前)に完了しており、サーバーは作り置いたHTMLを返すだけ。
この軸を踏まえたうえで、まず注意したいのは「React」と「Next.js/React Router」は別レイヤーだという点です。
React 自体は、SSR にも CSR にも使える、レンダリング方式に対して中立な UI ライブラリです。「React を使う= SPA で CSR」というイメージが根強いのは、React を学ぶ最初の一歩として広まった構成(ブラウザ側だけで完結する CSR 前提の構成)が長く定番だった歴史的な経緯によるもので、React というライブラリ自体の技術的な制約ではありません。
「Node.js でサーバーサイドレンダリングしているから大丈夫」という理解は方向性としては正しいですが、正確にはNode.js を使っていること自体ではなく、サーバー側で実際に React のレンダリング処理(renderToString など)を実行し、その結果の HTML をレスポンスとして返しているかが本質です。同じ Node.js サーバーでも、中身が空の HTML シェルだけを返し、実際の描画をブラウザ側の JavaScript に任せているなら、CSR と同じ弱点を抱えます。Next.js や React Router 7 のフレームワークモードは、この「サーバー側で React をレンダリングして HTML を返す」処理を、フレームワークとして自動的に組み込んでいるもの、と捉えると理解しやすくなります。
Next.js も React Router 7 も、何も特別な設定をせず標準の使い方をしていれば、上の図でいう SSR 寄り(サーバー側で本文入りの HTML を組み立ててから返す動作)になります。意図的に CSR だけの構成に寄せない限り、view-source の時点で本文が空になることは多くありません。逆に、データ取得や画面の組み立てをすべてブラウザ側の JavaScript に任せる構成にしてしまうと、フレームワークの種類にかかわらず CSR と同じ弱点を抱えます。
もう一つ、SPA でよく心配される点に「ページごとに URL が分かれず、検索エンジンが個別ページを発見できないのでは」というものがあります。これは /#/about のような ハッシュルーティングを使っていた時代の SPA に特有の弱点で、Next.js の App Router(ファイルベースのルーティング)も React Router 7(History API を使ったパスベースのルーティング)も、既定では実在するパス(/products/123 のような形)を使います。ハッシュルーティングをあえて選んでいない限り、この点は心配しなくて構いません。
URLはつねに1つ、#以降はサーバーに届かない
- 実在するのは https://example.com/ の1URLだけ
- #/about・#/contact はブラウザ内だけの状態でサーバーは関知しない
- ページごとの個別クロール・個別インデックスが難しい
ページごとに実在するURLが割り当たる
- /about・/products/123 がそれぞれサーバーへのリクエスト先になる
- Next.js App Router・React Router 7ともに既定でこの方式
- ページ単位でクロール・インデックス・サイトマップ掲載ができる
ここまでの構図は Next.js・React Router 7 に限った話ではありません。主要な UI ライブラリはどれも、単体では CSR(クライアントサイドレンダリング)前提で、SSR(サーバーサイドレンダリング)や SSG(静的サイト生成)を既定にした対応するメタフレームワークが別に存在するという、同じ2階建て構造を持っています(表中の CSR・SSR・SSG もこの略称です)。
メタフレームワークとは、UI ライブラリ(React・Vue.js など)の上に、ルーティング・サーバーサイドレンダリング・ビルド設定といった、アプリ全体に必要な仕組みをまとめて提供するフレームワークです。Next.js(React 向け)や Nuxt(Vue.js 向け)がこれにあたります。UI ライブラリ単体では「画面部品の描画」までしか面倒を見ないため、実際にサイトを1つ組み上げるには、ルーティングや SSR の仕組みを自分で用意するか、メタフレームワークに乗るかを選ぶことになります。
| UIライブラリ | ライブラリ単体(既定) | 対応するメタフレームワーク | メタフレームワークの既定 |
|---|---|---|---|
| React | CSR(React Router ライブラリモードなど) | Next.js/React Router 7(フレームワークモード) | SSR |
| Vue.js | CSR(Vue Router単体) | Nuxt | SSR(ssr: falseでSPAモードに変更可) |
| Svelte | CSR(クライアント側ルーティング) | SvelteKit | SSR(ページ単位でssr: falseも選べるが非推奨) |
| Angular | CSR(Angular Router、既定) | Angular(Angular CLIのSSR機能。旧Angular Universal) | SSR/Prerenderをルートごとに選択可(要オプトイン) |
| Solid.js | CSR(Solid Router単体) | SolidStart | SSR(既定。設定でCSRのみに変更可) |
| Qwik | SSR前提(resumabilityの設計上、CSR単体では成立しない) | QwikCity | SSR(既定。設計そのものがSSRを前提にする) |
| — | — | Astro | SSG(既定。アダプタ追加でページ単位のSSRも可) |
フロントエンド全般に共通する、この「ライブラリ単体で組んだか、SSR 既定のメタフレームワークに乗ったか」という選択を踏まえたうえで、ここから先は Google が実際にどう処理しているかを見ていきます。
02.Googlebotが見るのは「レンダリング後のHTML」
Google のJavaScript SEO の基礎によれば、Googlebot は JavaScript を使ったページをクロール → レンダリング → インデックスの3段階で処理します。クロールで生の HTML を取得したあと、レンダリングキューに送られ、実際に Chromium で JavaScript を実行してから、その結果をもとにインデックスします。
URLにアクセスし、サーバーが返す生のHTMLレスポンスを取得する。
取得したHTMLをレンダリングキューに送り、Chromiumでスクリプトを実行してDOMを完成させる。
レンダリング後のDOMをもとに、title・本文・構造化データなどを評価して登録する。
レンダリングは「同じキューでの2回目の処理」に近く、クロール直後に即座に完了するとは限らない。
重要なのは、レンダリングがクロールと同時に終わるわけではない点です。Google 自身も、レンダリングキューに入ったページは「数秒で処理されることが多いが、それより長くかかることもある」と説明しています。CSR に寄った構成では、この2段階目(レンダリング)が終わるまで、Googlebot にとってページの中身は空に近い状態が続くということです。SSR なら、この2段階目を待たず、1段階目(クロール)の時点ですでに本文が存在します。
クローラーとは?Googlebot・AIクローラー・robots.txtの関係を解説
Googlebotの仕組み、AIクローラーとの違い、robots.txtでできること・できないことを整理しています。
03.view-sourceとDevTools「Elements」で見え方が違う理由
Chrome で「ページのソースを表示」(view-source)と、開発者ツールの「Elements(要素)」パネルを見比べると、内容が違って見えることがあります。これは Next.js や React Router 特有の現象ではなく、ブラウザが2種類の異なる情報を表示しているために起きます。
| 表示方法 | 見ているもの | JavaScriptの実行 |
|---|---|---|
| ページのソースを表示(view-source) | サーバーが返した、生のHTTPレスポンス本文 | 実行しない(Googlebotのクロール段階と同じもの) |
| DevTools「Elements」 | JavaScript実行後、いま画面に描画されているDOMツリー | 実行済み(Googlebotのレンダリング段階に近いもの) |
SSR のページでは、view-source の時点ですでに title・h1・本文がテキストとして入っています。そのうえで、hydration に使うデータや、クリックなどの操作をブラウザ側で処理する部品(Next.js では Client Component と呼びます)用の JavaScript も同じ HTML に埋め込まれるため、生のソースを開くと本文と大量の script タグが混在した、読みにくい見た目になります。この「script が多くて読みにくい」状態と、「本文そのものが入っていない」状態は別物です。前者は SSR の正常な出力、後者が CSR 特有の弱点です。
本文がJavaScript実行後にしか入らない
- titleは既定値やid付きのdivのみ
- h1・本文はJSが実行されるまで存在しない
- GooglebotはレンダリングキューでJS実行を待つ必要がある
最初のレスポンスに本文が入っている
- title・meta・h1が最初から文字列として存在する
- ハイドレーション用のデータやscriptも同じHTMLに含まれる
- 本文はJavaScriptが生成するのを待たず、生HTMLの時点ですでに存在する
04.Next.js:SEOに強い設定・弱くなる設定
Next.js の App Router は、ファイルに何も指定しなければ、そのページが既定でServer Component(サーバー側で HTML の中身まで組み立てるコンポーネント)として動きます。つまり素直に組むだけで SSR の恩恵を受けられる、という設計です。そのページの SEO が弱くなるのは、次のような組み方で、本来サーバー側で済ませられるはずのデータ取得・描画をクライアント側の JavaScript に寄せすぎたときです。
見落としやすい設定
- title・meta・h1 の値を、マウント後の
useEffect内で API から取得して差し替えている(初回描画では既定値のまま)。 - JSON-LD などの構造化データを、クライアント側のみで生成して
headに注入している。 - h1 に相当する見出しが、ローディング表示の裏でクライアント側の状態管理を待ってから描画される。
これらはいずれも画面上は正しく動きますが、Googlebot のレンダリング完了を待つ前提に依存しています。SSR であれば、これらの値はサーバー側ですでに確定しているため、この種のリスク自体が発生しません。
構造化データとは?Entity(エンティティ)をAI SEOで伝えるSchema実装ガイド
JSON-LDの基本、Schema.orgの役割、企業サイト・記事メディアで実装すべきSchema一覧を整理しています。
generateMetadataはServer Component専用
Next.js 公式ドキュメント が明記するとおり、静的な metadata オブジェクトや動的な generateMetadata 関数は、Server Component からしかエクスポートできません。 page.tsxの先頭に "use client"を書いてしまうと、同じファイルから metadata をエクスポートできなくなります。
フォームの状態管理やモーダルなど、クライアント側の操作が多いページほど「ページ全体を Client Component にしてしまう」構成になりがちです。その結果、同じファイルに metadata を書けなくなり、諦めて省略される、あるいはクライアント側で document.title を書き換える対処に流れることがあります。後者は初回のレンダリング結果には反映されないため、SEO 上は改善になりません。
対処はシンプルで、ページのファイル自体はServer Componentのまま残し、クライアント側の処理だけを別ファイルのClient Componentに切り出す構成にします。
// app/products/[id]/page.tsx(Server Component)
import type { Metadata } from "next";
import { InteractiveComponent } from "./interactive-component";
export const metadata: Metadata = {
title: "商品ページ",
};
export default function Page() {
return <InteractiveComponent />;
}// app/products/[id]/interactive-component.tsx
"use client";
export function InteractiveComponent() {
// クライアント側の状態・イベントハンドラはこちら側に閉じ込める
}こうすると、page.tsxは Server Component のままなので metadata を宣言でき、インタラクティブな部分だけを Client Component に任せられます。
ストリーミングでmetadataが遅れて届くケース
Next.js は、外部データの取得に時間がかかる generateMetadata について、先に画面本体を送りつつ、metadata タグをあとから body 側に追記する「ストリーミングmetadata」という仕組みを持っています。Next.js の公式ドキュメントは、これがJavaScriptを実行してDOM全体を確認するボット(Googlebotなど)には正しく解釈されることを確認済みとしつつ、facebookexternalhit のようにJavaScriptを実行しないボットには従来どおり待たせる、と説明しています。
つまり Google 向けにはこの仕組み自体が問題になりにくい一方で、SNS のカード表示や一部の外部ツールのように JavaScript を実行しないクローラー向けには、metadata の反映が遅れて見えることがあります。
05.React Router 7:SEOに強い設定・弱くなる設定
React Router 7 は「ライブラリモード」と「フレームワークモード」の2つの使い方があり、どちらを選んでいるかで SEO への強さが変わります。
ライブラリモードとフレームワークモードの違い
react-router パッケージだけを使い、createBrowserRouter でルーティングを組む従来型の使い方(ライブラリモード)は、CSR 前提です。SSR を使いたい場合は、Vite プラグインを使うフレームワークモードを選びます。既定の弱点はここで決まり、ライブラリモードを選んでいる時点で、他の設定に関わらず view-source の時点では本文が空に近い状態になります。
ライブラリモードのまま title・meta を出し分けたい場合、react-helmet-async のようなライブラリで head タグを動的に書き換える方法もよく使われます。ただしこれはあくまで JavaScript 実行後にタグを差し替える仕組みで、view-source の時点では反映されません。CSR の根本的な弱点(本文が生 HTML に無い)を解消するものではなく、フレームワークモードへの移行までの応急処置と考えるのが実態に合っています。
ssr: true / false とprerenderの使い分け
フレームワークモードでは、 react-router.config.tsの ssr オプションで挙動を切り替えます。React Router 公式ドキュメント によれば、既定値は true で、ランタイムでサーバーレンダリングします。
// react-router.config.ts
import type { Config } from "@react-router/dev/config";
export default {
ssr: true, // 既定値。リクエストごとにサーバーレンダリングする
} satisfies Config;ssr: false にすると、ランタイムのサーバーレンダリングを止め、事前生成した index.html を配る SPA モードになります。ここで見落としやすいのは、ssr: false にしてもルートルート(レイアウトの最上位)自体はビルド時にサーバーレンダリングされるという点です。つまり view-source を開くと、ルートの title やレイアウト直下の内容は最初から入っていますが、その先の個別ルート(一覧・詳細ページなど)の title や本文は、ハイドレーション後のクライアント側ルーティングで初めて反映されます。
// react-router.config.ts
import type { Config } from "@react-router/dev/config";
export default {
ssr: false, // ランタイムSSRを無効化し、事前生成したindex.htmlを配るSPAにする
} satisfies Config;個別ルートまで検索エンジンに認識させたいのに SSR 用のサーバーは持ちたくない、という場合は prerenderオプション を使います。ビルド時に指定パスを静的 HTML 化する仕組みで、ssr の true / false どちらとも併用できます。
// react-router.config.ts
import type { Config } from "@react-router/dev/config";
const slugs = getPostSlugs();
export default {
prerender: [
"/",
"/blog",
...slugs.map((s) => `/blog/${s}`),
], // 指定パスをビルド時に静的HTML化する
} satisfies Config;06.よくある失敗
- ✓ 「Next.js/React Routerだから大丈夫」とレンダリング方式を確認せずに判断してしまう
- ✓ title・h1をuseEffect内でのAPI取得後にしか描画しない構成にしてしまう
- ✓ Next.jsのpage.tsxを丸ごとClient Componentにして、metadataの宣言場所を失ってしまう
- ✓ React Router 7でライブラリモードを使っているのに、フレームワークモードと同じSSRの恩恵があると思い込む
- ✓ React Router 7でssr: falseにしたあと、ルートルート以外の個別ページもSSRされていると思い込む
- ✓ view-sourceに大量のscriptが混ざっているのを見て、本文が無いと早合点する
07.よくある質問(FAQ)
SPA(シングルページアプリケーション)はSEOに不利ですか?
レンダリング方式次第です。データ取得も描画もすべてブラウザ側で行う CSR(クライアントサイドレンダリング)だけの構成では、最初の HTML に本文が入らず不利になりやすいですが、SSR を使っていれば、SPA という構造自体は不利になりません。Next.js の App Router や React Router 7 のフレームワークモードは、既定で SSR 寄りに動きます。
Next.jsやReact RouterのアプリはSPAと捉えて良いですか?
はい、SPA です。SPA は「サーバーへ新しい HTML をリクエストせず、JavaScript が DOM と URL を書き換えてページ遷移する」という遷移方式の話で、最初の1枚の HTML が CSR か SSR かとは別の軸です。Next.js・React Router 7 はどちらも、hydration 後のページ遷移はクライアントサイドルーティングで行われ、フルリロードは発生しないため、SSR を使っていても SPA という分類自体は変わりません。SEO に効くのは「SPA かどうか」ではなく「最初の HTML がどこで作られるか」です。
view-sourceがscriptタグばかりで、本文が無いように見えます。問題ですか?
script タグが多いこと自体は問題ではありません。SSR のページは、レンダリング済みの本文とあわせて、ハイドレーション用のデータや Client Component 用の JavaScript も同じ HTML に埋め込むため、生のソースは読みにくく見えます。確認すべきは、title・h1・本文のテキストが view-source の時点で文字列として存在しているかどうかです。
Next.jsを使っていれば自動的にSEOに強くなりますか?
自動的には強くなりません。App Router は既定で Server Components を使うため SSR 寄りに動きますが、ページ全体を Client Component にしてクライアント側でデータ取得・描画する構成にしてしまうと、CSR と同じ弱点を抱えます。generateMetadata が Server Component 専用である点も見落としやすいポイントです。
React Router 7はライブラリモードとフレームワークモードのどちらがSEOに向きますか?
検索エンジンに載せたいページがあるなら、フレームワークモードを選びます。ライブラリモード(createBrowserRouter でルーティングを組む従来型の使い方)は CSR 前提のため、view-source の時点で本文が空に近い状態になります。フレームワークモードは既定で SSR が有効です。
React Router 7のSPAモード(ssr: false)はSEOに使えますか?
検索に載せたい個別ページが多い場合は向きません。ssr: false にしてもルートルートはビルド時にサーバーレンダリングされますが、その先の個別ルートの内容はクライアント側のルーティングで初めて反映されます。個別ルートまで最初の HTML に含めたい場合は、prerender オプションで対象パスを静的 HTML 化することを検討します。
08.まとめ
Next.js・React Router 7 の SPA が SEO に弱くなるのは、フレームワーク自体の問題ではなく、CSR だけでページを組んだときです。SSR、React Router 7 の prerender を使っていれば、title・meta・h1・構造化データは最初のレスポンスの時点ですでに文字列として存在します。
フレームワークごとの弱点になりやすいポイントも押さえておきます。Next.js では generateMetadata が Server Component 専用であること、React Router 7 ではライブラリモードかフレームワークモードか・ssr の設定・prerender の対象範囲が、見落としやすい設定です。
リリース前の技術的なSEO診断や、継続的な改善の相談先を探している場合、自社だけで判断を固めるより、レンダリング方式の設計から一緒に見られるパートナーがいると安心です。弊社ではTANTOUというAI活用型の運用代行で、技術的なSEO診断からコンテンツ改善・継続的な計測まで支援しています。
TANTOU ─ 技術的SEO診断から継続的な改善まで支援
Next.js・React Routerなどのレンダリング方式を踏まえたリリース前SEO診断、title・meta・構造化データの実装確認、公開後の計測・改善まで、AIをフル活用した運用代行で支援します。単発の診断からもご相談いただけます。

