ReactでSPAを作ると、ページ遷移は滑らかで、状態管理や非同期通信もクライアント側で完結させやすくなります。一方で、公開ページを検索エンジンに見つけてもらいたい場合は、ブラウザでJavaScriptが動いた後だけではなく、最初に返すHTMLをどう作るかも重要です。

そこで候補になるのが、SSR(Server-Side Rendering)やプリレンダリングを扱えるNext.jsです。

ただし、「SPAはSEOできない」「Next.jsなら必ずSSRになる」と理解するのは正確ではありません。GoogleはJavaScriptを実行してページをレンダリングできますし、Next.js App RouterのServer Componentsも、リクエストごとに必ずSSRされるわけではありません。

この記事では、CSRとSSRの基本的な違いを整理したうえで、Next.js App Routerを使うと何が変わるのかを実装例とともに見ていきます。

この記事で扱うこと

主に次の内容を扱います。

  • CSRとSSRの5つの違い
  • CSRだけでもGoogle検索に表示される可能性がある理由
  • SSRやプリレンダリングがSEOで有利になりやすい理由
  • Next.js App Routerのルーティングとレイアウト
  • Server ComponentsとClient Componentsの使い分け
  • Metadata APIによるタイトルとdescriptionの設定
  • App Routerで初心者がつまずきやすい3つのポイント

Reactの基本は理解しているものの、CSRとSSRの違いやNext.jsのServer Componentsがまだ曖昧な人を想定しています。

なぜSPAだけではなくSSRやプリレンダリングも使われるのか

検索エンジン経由の公開ページではHTMLが重要になる

検索エンジンからの流入を狙うページでは、クローラーがページの内容を取得して理解できる必要があります。

従来型のWebサイトやSSRされたページでは、HTTPレスポンスとして返されるHTMLに本文が含まれています。クローラーはそのHTMLからタイトル、見出し、本文、リンクなどを読み取れます。

一方、CSR中心のSPAでは、最初のHTMLが次のような「アプリの入れ物」に近い構成になることがあります。

<body>
  <div id="root"></div>
  <script type="module" src="/src/main.jsx"></script>
</body>

実際の本文は、この後JavaScriptが実行されてからDOMへ追加されます。

Google SearchはJavaScriptを実行してレンダリングできるため、CSRだから検索結果に登録できないわけではありません。ただし、JavaScriptの取得や実行に失敗すると本文を取得できず、レンダリング待ちも発生します。また、すべてのクローラーやSNSのプレビュー生成BotがGooglebotと同じようにJavaScriptを実行できるとは限りません。

そのため、検索や共有を前提とする公開ページでは、重要な内容をサーバー側やビルド時にHTMLへ含めておく設計が扱いやすくなります。

アプリ内導線が中心ならSEOの優先度は下がる

すべてのWeb画面でSEOが重要になるわけではありません。

たとえば、ログイン後の管理画面、社内ツール、LINEやInstagram、TikTokなどのアプリ内導線から利用される画面では、検索エンジンから流入させる必要がない場合があります。そのような画面では、CSRの開発体験やインタラクションを優先する設計も自然です。

重要なのは「SPAかSSRか」を一律に決めることではなく、そのページを誰がどこから開くのかでレンダリング方法を選ぶことです。

AI検索でも「公開された読み取りやすいページ」は意味がある

情報探索では、Google検索だけでなく、ChatGPT、Gemini、Perplexityなどの生成AI・AI検索サービスを使う場面も増えています。

こうしたサービスがWeb上の情報を検索・参照する仕組みはそれぞれ異なりますが、公開ページがクロール可能で、本文やメタデータを機械的に読み取りやすいことは引き続き重要です。この文脈ではGEO(Generative Engine Optimization)などの呼び方もありますが、特定の実装だけでAIからの引用や掲載を保証できるものではありません。

SEOでもAI検索でも、まずは「公開した内容を取得できる状態にする」ことが前提になります。

CSRとSSRの5つの違い

ここからは、CSRとSSRの違いを5つに分けて整理します。

1. レンダリングする場所が違う

**CSR(Client-Side Rendering)**では、主なUIレンダリングをユーザーのブラウザで行います。

処理の流れを単純化すると、次のようになります。

ブラウザがページを要求

サーバーが最小限のHTMLとJavaScriptを返す

ブラウザがJavaScriptをダウンロード・実行

Reactが画面をレンダリング

Reactのエントリーポイントは、たとえば次のようになります。

// spa-demo/src/main.jsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.jsx";

// ブラウザ側で #root にReactをマウントする
createRoot(document.getElementById("root")).render(
  <StrictMode>
    <App />
  </StrictMode>,
);

**SSR(Server-Side Rendering)**では、リクエストに対してサーバー側でHTMLを生成して返します。

ブラウザが /todos を要求

サーバーが必要なデータを取得

サーバー側でHTMLを生成

本文を含むHTMLをブラウザへ返す

たとえば、最初のレスポンスに次のようなHTMLを含められます。

<h1>TODO</h1>
<ul>
  <li>Reactを学ぶ</li>
  <li>Next.jsを学ぶ</li>
</ul>

クライアントがJavaScriptを実行する前から本文がHTMLに存在する点が、CSR中心の構成との大きな違いです。

2. 初期表示までの処理が違う

CSRでは、画面の主要部分がJavaScriptに依存していると、次の処理を待つ必要があります。

  1. JavaScriptをダウンロードする
  2. JavaScriptを解析・実行する
  3. 必要ならAPIへデータを要求する
  4. ReactがDOMを生成する

端末性能やネットワーク状況によっては、コンテンツが見えるまで時間がかかります。

SSRやプリレンダリングでは、本文を含むHTMLを先に返せるため、JavaScriptの実行完了より前に内容を表示しやすくなります。

ただし、SSRなら必ずCSRより速いわけではありません。サーバー側の処理が重ければTTFB(Time to First Byte)が悪化しますし、インタラクティブになるまでにはクライアント側のJavaScriptも必要です。Next.jsでは静的レンダリング、キャッシュ、ストリーミングなども組み合わせて最適化します。

3. SEOでの扱いやすさが違う

CSR中心のページでもGoogle SearchはJavaScriptを実行できます。そのため、次のような説明は正確ではありません。

CSR = クローラーは空の <div id="root"></div> しか見えない

Googlebotはレンダリング処理を行い、JavaScript実行後のHTMLをインデックス登録に利用します。

一方で、SSRやプリレンダリングには次の利点があります。

  • 初期HTMLに本文を含められる
  • JavaScriptの実行に依存せず主要コンテンツを取得しやすい
  • JavaScriptを実行しないBotにも内容を伝えやすい
  • titleやdescriptionなどのメタ情報を安定して返しやすい
  • JavaScriptエラーによって本文全体が取得できなくなるリスクを減らせる

SEOだけを理由にすべてのページをSSRへ変更する必要はありませんが、検索流入が重要な公開ページではサーバー側レンダリングやプリレンダリングを検討する価値があります。

4. サーバー負荷の考え方が違う

CSR中心のSPAは、HTML、JavaScript、CSSなどの静的ファイルをCDNから配信し、APIを別に用意する構成にしやすいです。フロントエンドHTMLの生成処理は軽くできます。

動的SSRでは、リクエストごとにサーバー側でデータ取得やレンダリングが必要になるため、処理量が増えます。

ただし、Next.jsでは「Server Componentを使う = 毎回SSRする」ではありません。静的レンダリングやキャッシュが使われるケースもあります。

そのため、実際には次のように考える方が正確です。

  • CSRのみ:クライアント側の処理が増えやすい
  • 動的SSR:サーバー側の処理が増えやすい
  • 静的レンダリング:生成済みHTMLを配信できる
  • Next.js:ページごとにレンダリング戦略を組み合わせられる

5. HTMLが見えてから操作可能になるまでが違う

SSRで返されたHTMLが表示されても、Reactのイベントハンドラがまだ有効になっていない段階があります。

サーバーがHTMLを返す

ブラウザがHTMLを表示する

ReactのJavaScriptを読み込む

既存のHTMLとReactを結び付ける

ボタンなどが操作可能になる

この既存HTMLとクライアント側Reactを結び付ける処理が**hydration(ハイドレーション)**です。

「HTMLは表示されたが、まだクリックできない」という時間が長い場合は、送信するJavaScript量やClient Componentsの範囲を見直す必要があります。

CSRとSSRを比較するとどうなるか

比較項目CSR中心のSPASSR・サーバーレンダリング
主なレンダリング場所ブラウザサーバー
初期HTML本文が少ない場合がある本文を含めやすい
初期表示JavaScript実行待ちの影響を受けやすいHTMLを先に表示しやすい
SEOGoogleはJSを実行できるが依存要素が増える主要コンテンツを直接取得させやすい
サーバー負荷静的配信部分は軽くしやすい動的SSRではレンダリング負荷が増える
インタラクションReact起動後に操作可能HTML表示後、必要な部分はhydrationして操作可能

この表も絶対的な優劣ではありません。Next.jsのようなフレームワークでは、静的レンダリング、Server Components、Client Components、ストリーミングなどをページやコンポーネント単位で組み合わせます。

Next.js App Routerの基本

プロジェクトを作成する

Next.jsの新規プロジェクトはcreate-next-appで作成できます。

pnpm create next-app@latest next-demo --yes
cd next-demo
pnpm dev

2026年8月時点の公式ドキュメントでは、推奨デフォルトでTypeScript、ESLint、Tailwind CSS、App Router、Turbopackなどが有効になります。バージョンや初期設定は変わる可能性があるため、実際に作成するときはcreate-next-appの表示内容を確認してください。

ファイルシステムがルーティングになる

App Routerはファイルシステムベースのルーティングを採用しています。

app/
├── layout.tsx          # ルートレイアウト
├── page.tsx            # /
├── about/
│   └── page.tsx        # /about
└── dashboard/
    ├── layout.tsx      # dashboard配下の共通レイアウト
    ├── page.tsx        # /dashboard
    └── settings/
        └── page.tsx    # /dashboard/settings

React Routerでルート定義を手書きする構成と比べると、URL構造とディレクトリ構造の対応が分かりやすくなります。

項目React Routerを使うSPANext.js App Router
ルーティングRouteなどで定義app配下のフォルダとpage.tsx
共通レイアウトコンポーネント構成で管理layout.tsxをネスト可能
メタデータライブラリや独自実装を使う場合があるMetadata APIを利用可能

ルートレイアウトを作る

app/layout.tsxはアプリケーション全体の共通レイアウトです。

// app/layout.tsx
import type { Metadata } from "next";
import Link from "next/link";
import type { ReactNode } from "react";

export const metadata: Metadata = {
  title: "Next.js Demo",
  description: "Next.js App Routerのサンプルです。",
};

export default function RootLayout({ children }: { children: ReactNode }) {
  return (
    <html lang="ja">
      <body>
        <nav>
          <ul>
            <li>
              <Link href="/">ホーム</Link>
            </li>
            <li>
              <Link href="/about">About</Link>
            </li>
            <li>
              <Link href="/dashboard">ダッシュボード</Link>
            </li>
          </ul>
        </nav>

        {children}
      </body>
    </html>
  );
}

ここでは3点押さえておけば十分です。

  • metadataからtitleやdescriptionを定義できる
  • LinkでNext.jsのナビゲーションを利用できる
  • childrenへ現在のルートのページや下位レイアウトが入る

App RouterではServer Componentsがデフォルト

App Routerでは、page.tsxlayout.tsxはデフォルトでServer Componentsです。

// app/page.tsx
export default function Home() {
  return <h1>Hello Next.js</h1>;
}

Server Componentでは、コンポーネントの処理をサーバー側で実行でき、クライアントへ送るJavaScript量を減らしやすくなります。データベースやサーバー専用APIへ直接アクセスする設計にも向いています。

ただし、ここで注意したいのが、Server ComponentとSSRは同義ではないことです。

Server Componentは「どこでReactコンポーネントを実行するか」を表す仕組みです。一方、SSRは「リクエスト時にHTMLをサーバーで生成する」というレンダリング戦略です。Next.jsではServer Componentsを使いながら静的にレンダリング・キャッシュすることもあります。

操作が必要な部分だけClient Componentにする

useStateuseEffect、イベントハンドラ、windowなどのブラウザAPIが必要な場合はClient Componentを使います。

// app/Counter.tsx
"use client";

import { useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <button type="button" onClick={() => setCount((value) => value + 1)}>
      Count: {count}
    </button>
  );
}

ページ全体へ"use client"を付けるのではなく、操作が必要な部分を小さなClient Componentとして切り出すと、クライアントへ送るJavaScriptを抑えやすくなります。

ネストしたレイアウトを使う

ダッシュボード配下だけに共通ナビゲーションを付けたい場合は、app/dashboard/layout.tsxを作れます。

// app/dashboard/layout.tsx
import Link from "next/link";
import type { ReactNode } from "react";

export default function DashboardLayout({ children }: { children: ReactNode }) {
  return (
    <div>
      <nav>
        <Link href="/dashboard/settings">設定</Link>
      </nav>

      {children}
    </div>
  );
}

/dashboard/settingsへアクセスしたときの構造は次のようになります。

RootLayout
└── DashboardLayout
    └── Settings Page

Next.jsのレイアウトはナビゲーション時に状態を保持し、インタラクティブな状態を維持したまま再利用されます。公式ドキュメントでは、レイアウトはナビゲーション時に再レンダリングされないと説明されています。

Next.jsでSEOを考える3つの段階

SSRを使うこと自体がSEOの完成形ではありません。実際には、次の3段階で考えると整理しやすくなります。

1. ページが何について書かれているかを伝える

Next.jsではMetadata APIを利用できます。

import type { Metadata } from "next";

export const metadata: Metadata = {
  title: "Next.jsで学ぶCSRとSSRの違い",
  description: "CSRとSSRの違いとSEOへの影響を解説します。",
};

metadatagenerateMetadataを使うと、Next.jsが対応する<head>要素を生成します。

meta keywordsはGoogle Searchのインデックス登録やランキングには使われないため、Google向けSEOを目的に設定する必要はありません。

2. URLごとに意味のある本文を返す

ブログなら、各記事が独立したURLを持ち、そのURLから記事本文を取得できる状態にします。

/posts/react-basics
  → React基礎の記事

/posts/nextjs-ssr
  → Next.jsとSSRの記事

/posts/web-seo
  → Web SEOの記事

QiitaやZennのようなコンテンツサイトをイメージすると分かりやすいでしょう。各記事に固有URLがあり、それぞれのページが検索対象になります。

ここでも大切なのはSSRという名前ではなく、そのURLを取得したときに、クローラーがページ固有の内容へ到達できることです。

3. コンテンツ自体の品質を上げる

検索エンジンがページを取得できても、それだけで上位表示されるわけではありません。

少なくとも次の要素が必要です。

  • 検索した人の疑問に答えている
  • タイトルと本文の内容が一致している
  • 他ページの言い換えだけではない
  • 必要な前提条件や制約が明記されている
  • 内部リンクで関連ページへ移動できる
  • ページが安定して取得できる

「インデックス可能なページ数が増えるほどサイト全体の評価が自動的に上がる」といった単純な式では考えない方が安全です。低品質なページを大量に増やすより、読者にとって意味のあるページを継続して整える方が重要です。

App Routerで初心者がつまずきやすい3つのポイント

1. Server ComponentでuseStateを使う

次のコードはServer Componentのままでは動きません。

// ❌ Server ComponentではuseStateやonClickを使えない
import { useState } from "react";

export default function Page() {
  const [count, setCount] = useState(0);

  return (
    <button type="button" onClick={() => setCount(count + 1)}>
      {count}
    </button>
  );
}

操作が必要な部分はClient Componentへ切り出します。

// app/page.tsx
import Counter from "./Counter";

export default function Page() {
  return <Counter />;
}
// app/Counter.tsx
"use client";

import { useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <button type="button" onClick={() => setCount((value) => value + 1)}>
      {count}
    </button>
  );
}

ページ全体をClient Componentにする必要がなければ、インタラクションが必要な部分だけを分離する方が扱いやすくなります。

2. Server Componentでwindowdocumentを使う

windowdocumentlocalStorageなどはブラウザAPIです。Server Componentの実行環境では利用できません。

画面幅のようなブラウザ情報を使う場合は、Client Componentで取得します。

"use client";

import { useEffect, useState } from "react";

export default function ScreenWidth() {
  const [width, setWidth] = useState<number | null>(null);

  useEffect(() => {
    const updateWidth = () => setWidth(window.innerWidth);

    updateWidth();
    window.addEventListener("resize", updateWidth);

    return () => window.removeEventListener("resize", updateWidth);
  }, []);

  if (width === null) {
    return <p>画面幅を確認しています。</p>;
  }

  return <p>画面幅: {width}px</p>;
}

typeof window !== "undefined"で分岐すればエラーを避けられる場面はありますが、それだけでブラウザ依存のUI設計が解決するわけではありません。Client Componentにするべき処理かどうかを先に判断した方が分かりやすくなります。

3. layout.tsxがページ遷移のたびに再レンダリングされると思う

App Routerのレイアウトは、クライアントナビゲーション時に再利用され、ページ遷移ごとに再レンダリングされません。

そのため、現在のpathnameや検索パラメータなど、遷移によって変化する値をレイアウトで直接扱おうとすると期待とずれる場合があります。

安定した共通UIはlayout.tsxへ置き、ルートごとに変わるデータはpage.tsxや適切なClient Componentへ分けます。

// app/dashboard/layout.tsx
import type { ReactNode } from "react";

export default function Layout({ children }: { children: ReactNode }) {
  return (
    <div>
      <nav>共通ナビゲーション</nav>
      {children}
    </div>
  );
}
// app/dashboard/page.tsx
export default async function Page() {
  const response = await fetch("https://api.example.com/data");
  const data = await response.json();

  return <pre>{JSON.stringify(data, null, 2)}</pre>;
}

データのキャッシュや再検証は別の論点になるため、実際のアプリではデータの更新頻度に合わせてNext.jsのキャッシュ戦略も設計します。

まとめ

CSRとSSRの違いを一言でまとめるなら、「主要なUIをどこで生成し、最初のHTMLにどこまで含めるか」の違いです。

特に押さえておきたい点は次のとおりです。

  • CSRはブラウザ側でReactを実行して画面を作る
  • SSRはサーバー側でHTMLを生成して返す
  • GoogleはJavaScriptを実行できるため、CSRだけでもインデックス登録される可能性がある
  • SSRやプリレンダリングは、主要コンテンツをJavaScript実行前から取得できる状態にしやすい
  • SSRでHTMLを表示した後、操作が必要な部分ではhydrationが行われる
  • Next.js App Routerではpage.tsxlayout.tsxがデフォルトでServer Componentsになる
  • Server Componentと「リクエストごとのSSR」は同じ意味ではない
  • useStateやブラウザAPIが必要な部分だけClient Componentにする
  • SEOはレンダリング方式だけではなく、メタデータ、URL設計、本文品質まで含めて考える

SPAの操作性と、サーバー側レンダリングの取得しやすさは二者択一ではありません。Next.jsでは、サーバーで処理する部分とクライアントで操作する部分を分けながら、ページごとに必要なレンダリング戦略を選べます。

まずは「検索から見つけてもらう公開ページなのか」「ログイン後のアプリ画面なのか」を整理し、そのページに必要なレンダリング方法を選ぶのが実践的です。