<CodeLearn/>
Next.js レッスン5

レンダリング戦略

SSR、SSG、ISR、CSRの違いを理解し、最適なレンダリング方法を選択しよう

4つのレンダリング戦略

Next.jsでは、ページやコンポーネントごとに異なるレンダリング戦略を選択できます。 それぞれの特徴を理解し、コンテンツの性質に合わせて最適な方法を選びましょう。

SSG(静的サイト生成)

ビルド時にHTMLを事前生成。CDNから配信でき最も高速。

適用例: ブログ記事、ドキュメント、ランディングページ

SSR(サーバーサイドレンダリング)

リクエストごとにサーバーでHTMLを生成。常に最新データ。

適用例: ダッシュボード、検索結果、ユーザー固有のページ

ISR(増分静的再生成)

静的生成 + 定期的な再生成。速度と鮮度のバランス。

適用例: ECサイトの商品ページ、ニュースサイト

CSR(クライアントサイドレンダリング)

ブラウザ側でJSを実行してUIを構築。高インタラクティブ。

適用例: リアルタイムチャット、地図アプリ、管理画面の一部

SSG(静的サイト生成)

Next.jsのApp Routerでは、データ取得がないか、キャッシュされたfetchを使う場合、 自動的に静的レンダリング(SSG)になります。ビルド時にHTMLが生成され、CDNから配信されます。

// 静的レンダリング(SSG)の例
// fetchがない、または cache: "force-cache" の場合、自動でSSGになる

// 例1: データ取得なし → 自動的に静的
export default function AboutPage() {
  return (
    <div>
      <h1>会社について</h1>
      <p>私たちは...</p>
    </div>
  );
}

// 例2: キャッシュありのfetch → ビルド時に1回だけ取得
export default async function BlogPage() {
  // デフォルトでキャッシュされる(静的レンダリング)
  const res = await fetch("https://api.example.com/posts");
  const posts = await res.json();

  return (
    <ul>
      {posts.map((post: { id: number; title: string }) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

// 例3: 動的ルートで静的ページを事前生成
// generateStaticParams で、ビルド時に生成するページを指定
export async function generateStaticParams() {
  const res = await fetch("https://api.example.com/posts");
  const posts = await res.json();

  // 各記事のslugを返す → ビルド時にすべてのページが生成される
  return posts.map((post: { slug: string }) => ({
    slug: post.slug,
  }));
}

export default async function BlogPost({
  params,
}: {
  params: { slug: string };
}) {
  const res = await fetch(
    `https://api.example.com/posts/${params.slug}`
  );
  const post = await res.json();

  return <article><h1>{post.title}</h1></article>;
}

SSR(サーバーサイドレンダリング)

リクエストごとに最新のデータが必要な場合は、動的レンダリング(SSR)を使います。cache: "no-store"を指定するか、 動的関数(cookies、headersなど)を使うと自動的にSSRになります。

// 動的レンダリング(SSR)の例

// 方法1: cache: "no-store" を指定
export default async function DashboardPage() {
  // リクエストごとに最新データを取得
  const res = await fetch("https://api.example.com/dashboard", {
    cache: "no-store",
  });
  const data = await res.json();

  return (
    <div>
      <h1>ダッシュボード</h1>
      <p>売上: {data.revenue}円</p>
      <p>注文数: {data.orders}件</p>
    </div>
  );
}

// 方法2: 動的関数を使う(自動的にSSRになる)
import { cookies, headers } from "next/headers";

export default async function ProfilePage() {
  // cookies() を呼ぶだけで動的レンダリングになる
  const cookieStore = cookies();
  const token = cookieStore.get("auth-token");

  const res = await fetch("https://api.example.com/me", {
    headers: { Authorization: `Bearer ${token?.value}` },
  });
  const user = await res.json();

  return <div>こんにちは、{user.name}さん</div>;
}

// 方法3: ページ単位で強制的に動的レンダリング
export const dynamic = "force-dynamic";

export default async function RealtimePage() {
  // ...
}

ISR(増分静的再生成)

ISRは、SSGの高速さとSSRのデータ鮮度を両立させる戦略です。 一定時間はキャッシュを使い、期限が切れたら裏側で再生成します。next: { revalidate: 秒数 }で設定します。

// ISR(増分静的再生成)の例

export default async function NewsPage() {
  // 60秒ごとに再検証(再生成)
  const res = await fetch("https://api.example.com/news", {
    next: { revalidate: 60 },
  });
  const articles = await res.json();

  return (
    <div>
      <h1>ニュース</h1>
      {articles.map((article: { id: number; title: string; summary: string }) => (
        <article key={article.id}>
          <h2>{article.title}</h2>
          <p>{article.summary}</p>
        </article>
      ))}
    </div>
  );
}

// ISRの動作フロー:
// 1. 最初のアクセス: 静的HTMLを生成してキャッシュ
// 2. 60秒以内のアクセス: キャッシュされたHTMLを返す(超高速)
// 3. 60秒後のアクセス:
//    - まずキャッシュされたHTMLを返す(ユーザーは待たない)
//    - 裏側で新しいHTMLを再生成
// 4. 次のアクセス: 新しいHTMLが返される

// ページ単位でrevalidateを設定することも可能
export const revalidate = 60; // ページ内の全fetchに適用

// オンデマンド再検証(タグベース)
// route.ts やServer Actionから手動で再検証をトリガー
import { revalidateTag } from "next/cache";

// データ取得時にタグを付ける
const res = await fetch("https://api.example.com/products", {
  next: { tags: ["products"] },
});

// 商品が更新されたときに手動で再検証
// (API RouteやServer Actionから呼ぶ)
revalidateTag("products");

ストリーミングとSuspense

ストリーミングは、サーバーからクライアントにHTMLを段階的に送信する仕組みです。 ページの一部がまだ読み込み中でも、準備ができた部分から順に表示されます。 これにより、ユーザーは早い段階でページの内容を見始めることができます。

import { Suspense } from "react";

// 遅いAPI(3秒かかる)
async function SlowRecommendations() {
  const res = await fetch("https://api.example.com/recommendations", {
    cache: "no-store",
  });
  const items = await res.json();
  return (
    <ul>
      {items.map((item: { id: number; name: string }) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

// 速いAPI(0.1秒で返る)
async function ProductInfo({ id }: { id: string }) {
  const res = await fetch(`https://api.example.com/products/${id}`);
  const product = await res.json();
  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.price}円</p>
    </div>
  );
}

// ページコンポーネント
export default function ProductPage({
  params,
}: {
  params: { id: string };
}) {
  return (
    <div>
      {/* 商品情報は即座に表示 */}
      <Suspense fallback={<p>商品情報を読み込み中...</p>}>
        <ProductInfo id={params.id} />
      </Suspense>

      {/* おすすめは遅いので、スケルトンを表示して待つ */}
      <Suspense
        fallback={
          <div className="animate-pulse space-y-2">
            <div className="h-4 bg-gray-200 rounded w-3/4"></div>
            <div className="h-4 bg-gray-200 rounded w-1/2"></div>
            <div className="h-4 bg-gray-200 rounded w-5/6"></div>
          </div>
        }
      >
        <SlowRecommendations />
      </Suspense>
    </div>
  );
}

// 従来のSSR: 全データが揃うまでページ全体が表示されない
// ストリーミング: 準備できた部分から順にHTMLが送信される
// → ユーザー体験が大幅に向上

レンダリング戦略の使い分け

どのレンダリング戦略を使うかは、データの更新頻度とパフォーマンス要件で決まります。

戦略速度データ鮮度適したコンテンツ
SSG最速ビルド時のみブログ、ドキュメント、LP
ISR高速定期更新ECサイト、ニュース
SSRやや遅い常に最新ダッシュボード、検索結果
CSR初期表示遅いリアルタイムチャット、リッチUI

まとめ

  • SSGはビルド時にHTMLを生成し、最も高速に配信できる
  • SSRはリクエストごとにサーバーで生成し、常に最新データを表示できる
  • ISRはSSGとSSRの中間で、定期的にページを再生成する
  • CSRはブラウザ側でレンダリングし、高いインタラクティブ性を実現する
  • Suspenseを使ったストリーミングで、段階的にUIを表示できる
  • コンテンツの性質に応じて、適切なレンダリング戦略を選択することが重要