はじめに
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"で実行します。
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になります。
export default function SsgPage() {
return <div>SsgPage</div>;
}
もしくは次のようにfetch() に cache: "force-cache" オプションを指定すると、SSGになります。
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: 秒数 }を指定して実行します。
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を追加します。
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
cacheComponents: true,
};
export default nextConfig;
次にfetch()でデータ取得する部分を別コンポーネントとして切り出し、呼び出す際にSuspenseでラップします。
こうすることで静的な部分(ビルド時にHTMLが生成される)と動的な部分(リクエストの度にHTMLが生成される)が区別され、PPRとしてレンダリングされます。
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では、新たなメンバーを募集しています。
日本一のアウトプットコミュニティでキャリアアップしませんか?
興味のある方は、ぜひホームページをのぞいてみてください!
▼▼▼