
Next.js 16 の Cache Components と PPR - キャッシュを「明示的」に組み直す
Next.js の App Router は強力ですが、「いつ・何がキャッシュされているのか分かりにくい」というのが長年の不満でした。fetch が暗黙にキャッシュされ、意図せず古いデータが出たり、逆にキャッシュが効かなかったり——。
Next.js 16 の Cache Components は、この前提をひっくり返します。暗黙キャッシュをやめ、「キャッシュするものだけ明示する」opt-in 方式へ。あわせて Partial Prerendering(PPR)で「静的な部分と動的な部分を1ページ内で混ぜる」のが標準になります。このブログ(Next.js 構成)にも直結する話を、公式ドキュメントをもとに整理します。
発想の転換: 暗黙キャッシュをやめる
next.config で cacheComponents: true を有効にすると、PPR と use cache ディレクティブが一緒に有効になります。
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
cacheComponents: true,
}
export default nextConfig最大の変化はこれです。Cache Components 有効時、暗黙的なキャッシュは廃止され、ページ・レイアウト・ルートハンドラ内の動的コードはデフォルトで毎リクエスト実行されます。キャッシュは「明示的に有効化するもの(opt-in)」になりました。「気づかぬうちにキャッシュされていた」が無くなる代わりに、「キャッシュしたい場所は自分で宣言する」のが基本になります。
NOTE
旧来の experimental.dynamicIO は cacheComponents にリネームされ、experimental.useCache も統合されました。experimental.ppr フラグやルートごとの export const experimental_ppr も削除され、すべて cacheComponents に集約されています。
use cache: キャッシュしたい場所を宣言する
キャッシュは 'use cache' ディレクティブで宣言します。粒度は3レベルです。
- ファイルレベル: ファイル先頭に書くと、その全 export 関数が対象(すべて async 必須)
- コンポーネントレベル: コンポーネント関数の先頭に書く
- 関数レベル: 任意の async 関数(fetch・DB クエリ・重い計算など)の先頭に書く
import { cacheLife, cacheTag } from 'next/cache'
export async function getPosts() {
'use cache'
cacheLife('hours') // 有効期間プロファイル
cacheTag('posts') // タグ付けしてオンデマンド無効化
const res = await fetch('https://api.example.com/posts')
return res.json()
}キャッシュキーは「ビルド ID + 関数の場所・シグネチャのハッシュ + シリアライズ可能な引数」から自動生成されます。外側スコープの変数も引数としてキーに含まれます。
WARNING
キャッシュの内側では cookies()・headers()・searchParams といったリクエスト時 API を直接使えません。使いたい場合はキャッシュの外で読み、引数として渡すのが定石です(その値がキャッシュキーに加わる)。引数・戻り値はシリアライズ可能である必要があり、クラスインスタンスや関数などは渡せません。ここを外すと、ビルドが約50秒でタイムアウトするなどハマりやすいので注意です。
PPR: 静的シェル+動的ストリーミング
Cache Components を有効にすると、Partial Prerendering(PPR)がデフォルト挙動になります。PPR は、静的な HTML「シェル」を即座に返し、動的な部分を同じレスポンス内でストリーミングする方式です。ページ単位ではなくコンポーネント単位で静的・動的を混ぜられます。
考え方はシンプルで、「<Suspense> の外側は静的、内側は動的」。
import { Suspense } from 'react'
export default function BlogPage() {
return (
<>
<header>{/* 決定的: 自動で静的シェルに入る */}</header>
<BlogPosts /> {/* use cache: 静的シェルに含む */}
<Suspense fallback={<p>Loading...</p>}>
<UserPreferences /> {/* cookie 依存: リクエスト時にストリーム */}
</Suspense>
</>
)
}
async function BlogPosts() {
'use cache'
cacheLife('hours')
const res = await fetch('https://api.example.com/posts')
return /* ... */
}
async function UserPreferences() {
const theme = (await cookies()).get('theme')?.value ?? 'light'
return /* ... */
}ビルド時、Next.js はツリーをたどり、use cache の結果と決定的な処理を静的シェルに含め、<Suspense> の中身はリクエスト時にストリームします。
NOTE
注意点として、未キャッシュのデータにアクセスするコンポーネントを <Suspense> でも use cache でも包まないと、Uncached data was accessed outside of <Suspense> エラーになります。v16 は「どちらに倒すか」を明示させる設計です。逆に、同期処理だけのコンポーネントは <Suspense> で包んでも静的に解決されます(<Suspense> が動的化のスイッチではない)。
設計の三分類と、リバリデーションの使い分け
実務では、コンポーネントを次の3つに振り分けるのが基本です。
| 種類 | 例 | 扱い |
|---|---|---|
| 静的(決定的のみ) | ヘッダー・ナビ | 何もしない(自動で静的シェル) |
| 全ユーザー共通の動的 | 記事一覧・カタログ | use cache + cacheLife + cacheTag |
| リクエスト固有 | cookie 依存のユーザー設定 | <Suspense> でストリーム |
無効化(リバリデーション)の API も整理されました。
revalidateTag(tag, profile): stale-while-revalidate。多少の遅延が許せるもの(ブログ・カタログ)。v16 では第2引数のプロファイルが必須に(例:revalidateTag('posts', 'max'))updateTag(tag)(Server Actions 専用・新設): read-your-writes。フォーム送信後など即時反映が要る場面refresh()(Server Actions 専用・新設): 未キャッシュデータだけを更新(キャッシュには触れない)。通知カウントなど
v15 から v16 への移行で詰まりやすい点
移行は codemod で大半を自動化できます。
npx @next/codemod@canary upgrade latest主な注意点です。
- 要件: Node.js 20.9 以上、TypeScript 5.1 以上、モダンブラウザ(Chrome/Edge/Firefox 111+、Safari 16.4+)
- Async Request API:
cookies()/headers()/draftMode()、params/searchParamsは必ずawait(v15 の同期互換は廃止) - Turbopack がデフォルト:
--turbopackフラグ不要。カスタム webpack 設定があるとnext buildが失敗するので--webpackで明示オプトアウト middleware.ts→proxy.ts(関数名もproxy。Node ランタイム固定で edge 非対応)- PPR の挙動が v15 canary と異なる: v15 canary で PPR を使っているなら、いったんそこに留まり、公式の「Migrating to Cache Components」ガイドの移行パターンに従う。
experimental.ppr: trueはそのままでは v16 で機能しない revalidateTagは第2引数必須に。unstable_cacheLife/unstable_cacheTagは安定化(プレフィックス不要)
NOTE
画像まわりのデフォルトも変わっています(Cache Components とは別だが移行注意)。images.minimumCacheTTL が60秒→4時間、images.qualities が全許可→[75] のみ、images.domains が非推奨(remotePatterns へ)など。アップグレード後に画像の挙動が変わって見えたらここを確認してください。
まとめ
- Next.js 16 の Cache Components(
cacheComponents: true)は、暗黙キャッシュをやめ「キャッシュするものだけuse cacheで明示する」opt-in 方式 use cacheはファイル/コンポーネント/関数の3粒度。内側でcookies()等は使えず、外で読んで引数で渡す- PPR は静的シェル+動的ストリーミング。「
<Suspense>の外は静的、内は動的」。未キャッシュデータは Suspense か use cache で必ず包む - 無効化は
revalidateTag(遅延可・プロファイル必須)/updateTag(即時・Server Actions)/refreshを使い分け - 移行は codemod。Node 20.9+、
await必須、Turbopack 既定、middleware→proxy、PPR は v15 canary と非互換に注意
「キャッシュは賢く自動で」から「キャッシュは明示的に宣言する」へ。最初は手間に見えますが、「どこがなぜキャッシュされるか」がコードから読めるようになるのが Cache Components の狙いです。