0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Next.js】レンダリング方式条件の早見表

0
Last updated at Posted at 2026-02-27

はじめに

Next.jsにある様々なレンダリング方式とその条件をよく間違えるので早見表を作りましたG

環境

Next : 16.1.6
React : 19.2.3

早見表

方式 レンダリングタイミング 動的レンダリングか
SSG ビルド時
ISR ビルド時 + revalidate時 △(静的寄り)
PPR ビルド時(静的部分) + リクエスト時(動的部分) 部分的に✓
SSR リクエスト時

@honey32さんにご助言いただきダイナミックレンダリングになる条件を修正しました。

レンダリング方式 条件
SSG 外部データ取得なし、または fetch()cache: "force-cache" オプションを使用
SSR cookies / headers / connection / draftMode / unstable_noStore を使用
searchParams prop を使用
fetch(){ cache: 'no-store' } を指定
ISR fetch(){ next: { revalidate: 秒数 } } オプションを使用
PPR cacheComponents: true かつ Suspense で動的部分を囲む

SSR

SSRでレンダリングさせたい場合は該当コンポーネント上で fetch()メソッドをcache: "no-store"で実行します。

app/ssr/page.tsx
interface User {
  id: string;
  name: string;
}

export default async function SsrPage() {
  const getUsers = async () => {
    const res = await fetch("https://jsonplaceholder.typicode.com/users", {
      cache: "no-store", // ← 毎回サーバーを見に行く
    });
    return (await res.json()) as User[];
  };

  const users = await getUsers();

  return (
    <div>
      <h1>ユーザ一覧</h1>
      {users.map((users) => (
        <div key={users.id}>{users.name}</div>
      ))}
    </div>
  );
}

SSRになっていればnext buildで該当フォルダに(Dynamic) server-rendered on demandと表記されます。

Route (app)
┌ ○ /
├ ○ /_not-found
└ ƒ /ssr

ƒ  (Dynamic)  server-rendered on demand

SSG

次のようにfetch()などをしない場合はSSGになります。

app\ssg\page.tsx
export default function SsgPage() {
  return <div>SsgPage</div>;
}

もしくは次のようにfetch()cache: "force-cache" オプションを指定すると、SSGになります。

app\ssg\page.tsx
interface User {
  id: string;
  name: string;
}

export default async function SsgPage() {
  const getUsers = async () => {
    const res = await fetch("https://jsonplaceholder.typicode.com/users", {
      cache: "force-cache",
    });
    return (await res.json()) as User[];
  };

  const users = await getUsers();

  return (
    <div>
      <h1>ユーザ一覧</h1>
      {users.map((users) => (
        <div key={users.id}>{users.name}</div>
      ))}
    </div>
  );
}

next buildすると(Static) prerendered as static contentと表示されます。

Route (app)
┌ ○ /
├ ○ /_not-found
└ ○ /ssg

○  (Static)   prerendered as static content

ISR

ISRでレンダリングさせたい場合は該当コンポーネント上で fetch()メソッドにnext: { revalidate: 秒数 }を指定して実行します。

app\isr\page.tsx
interface User {
  id: string;
  name: string;
}

export default async function SsgPage() {
  const getUsers = async () => {
    const res = await fetch("https://jsonplaceholder.typicode.com/users", {
      next: { revalidate: 10 }, // ←10秒ごとにHTMLを再生成
    });
    return (await res.json()) as User[];
  };

  const users = await getUsers();

  return (
    <div>
      <h1>ユーザ一覧</h1>
      {users.map((users) => (
        <div key={users.id}>{users.name}</div>
      ))}
    </div>
  );
}

next buildで(Static) prerendered as static contentと合わせてRevalidate(キャッシュの再検証時間)Expireキャッシュの最大有効期限)が表示されればISRでレンダリングされています。

Route (app)      Revalidate  Expire
┌ ○ /
├ ○ /_not-found
└ ○ /isr                10s      1y

○  (Static)   prerendered as static content

PPR

まずnext.config.tsにcacheComponents: trueを追加します。

next.config.ts
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  cacheComponents: true,
};

export default nextConfig;

次にfetch()でデータ取得する部分を別コンポーネントとして切り出し、呼び出す際にSuspenseでラップします。
こうすることで静的な部分(ビルド時にHTMLが生成される)と動的な部分(リクエストの度にHTMLが生成される)が区別され、PPRとしてレンダリングされます。

app\ppr\page.tsx
import { Suspense } from "react";

interface User {
  id: string;
  name: string;
}

async function UserList() {
  const res = await fetch("https://jsonplaceholder.typicode.com/users");
  const users = (await res.json()) as User[];

  return (
    <>
      {users.map((users) => (
        <div key={users.id}>{users.name}</div>
      ))}
    </>
  );
}

export default async function PprPage() {
  return (
    <div>
      <h1>ユーザ一覧</h1>
      <Suspense fallback={<div>ロード中...</div>}>
        <UserList />
      </Suspense>
    </div>
  );
}

PPRはnext buildで◐ (Partial Prerender) と表記されます。

Route (app)
┌ ◐ /
├ ○ /_not-found
└ ◐ /ppr

○  (Static)             prerendered as static content
◐  (Partial Prerender)  prerendered as static HTML with dynamic server-streamed content

おわりに

レンダリングの条件は複雑なようですが、一度整理すると明確に違いがわかりました。今までなんとなくで使っていたせいで定着がしていなかったんだと反省です。

個人的にはPPRが万能な気がしますが、CDNキャッシュができないという話も聞くのでその点も今後確認していきます。

参考

https://react.dev/reference/react/useEffect
https://nextjsjp.org/docs/app/getting-started/server-and-client-components
https://nextjsjp.org/docs/app/api-reference/directives/use-client
https://dev.classmethod.jp/articles/nextjs-rendering/
https://nextjs.org/docs/app/guides/caching#dynamic-rendering

JISOUのメンバー募集中!

プログラミングコーチングJISOUでは、新たなメンバーを募集しています。
日本一のアウトプットコミュニティでキャリアアップしませんか?
興味のある方は、ぜひホームページをのぞいてみてください!
▼▼▼

0
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?