はじめに
Next.js の App Router は、静的・動的レンダリングを細かく制御するための非常に洗練されたキャッシュ機構を備えています。
しかし、キャッシュの種類が多く、それぞれ「どこで」「何を」「どれくらいの期間」保持するのかが直感的に理解しづらいため、「データが更新されたはずなのに古い画面が表示されたままになる」「意図しないAPIコールが発生してパフォーマンスが低下する」といった課題が多々発生します。
本記事では、Next.js 14 / 15 で運用する上で避けて通れない 4つのキャッシュ機構 の関係性をスッキリ整理し、適切な制御・再検証(revalidation)方法を解説します。
1. Next.js App Router の4つのキャッシュ
Next.js App Router は、以下の4層のキャッシュが組み合わさって動作しています。
【クライアント側】
1. Router Cache (ブラウザ内一時キャッシュ)
│
【サーバー側】
2. Request Memoization (Reactレンダリング中の一時メモリ)
│
3. Data Cache (複数リクエスト間で永続するAPIデータ)
│
4. Full Route Cache (ビルドされた静的HTML/RSCデータ)
それぞれの特性を表にまとめました。
| キャッシュ名 | 保存場所 | 対象データ | 生存期間 | 無効化・再検証方法 |
|---|---|---|---|---|
| 1. Router Cache | クライアント | RSCペイロード | セッション中(数分) | router.refresh(), revalidatePath |
| 2. Request Memoization | サーバー | fetch の戻り値 | 1レンダリングサイクル | 自動(リクエスト完了時にクリア) |
| 3. Data Cache | サーバー(永続) | fetch レスポンス | 永続(手動リフレッシュ) | revalidatePath, revalidateTag |
| 4. Full Route Cache | サーバー(永続) | HTML & RSCデータ | 永続 | ビルドし直し, Data Cache再検証と連動 |
2. 各キャッシュの仕組みと特徴
① Request Memoization(リクエスト・メモ化)
同一のレンダリングツリー内で、同じURL・同じオプションの fetch リクエストが複数回呼び出された場合、最初の1回だけが実際に実行され、残りはキャッシュから返される仕組みです。
// コンポーネントA
async function UserProfile() {
const res = await fetch('https://api.example.com/user'); // 実際に実行される
return <div>...</div>;
}
// コンポーネントB(同階層やネスト下)
async function UserSettings() {
const res = await fetch('https://api.example.com/user'); // キャッシュ(メモ化)から即座に返る
return <div>...</div>;
}
- メリット: コンポーネントツリーの上部でデータをフェッチしてPropsで下にバケツリレーする必要がなくなります。各コンポーネントが自前で
fetchを呼んでもパフォーマンス劣化が起こりません。
② Data Cache(データキャッシュ)
サーバー側で永続的にデータを保持するキャッシュです。複数のユーザー、複数のリクエスト(ページ遷移)にまたがってデータをキャッシュし続けます。
Next.jsのデフォルトの fetch は、特別な指定がない限り、データをData Cacheに格納します(※Next.js 15ではデフォルトでキャッシュしないように仕様変更されていますが、必要に応じて明示的設定が可能です)。
- 再検証 (Revalidation):
時間ベース、またはオンデマンド(タグ指定など)で再検証できます。
// 3600秒間キャッシュを保持する (時間ベース) fetch('https://api.example.com/products', { next: { revalidate: 3600 } });
③ Full Route Cache(フルルート・キャッシュ)
ビルド時、または再検証時に、ルート(ページ)全体のHTMLおよびRSC(React Server Component)ペイロードをサーバー側で生成し、キャッシュする仕組みです。これはいわゆる「静的生成(SSG)」に相当します。
- 動的関数(Dynamic Functions)の検知:
ルート内で
cookies()やheaders()、あるいはsearchParamsのような動的データにアクセスした場合、Full Route Cache は自動的にスキップされ、リクエストごとにオンデマンドでページが生成されるようになります。
④ Router Cache(ルーターキャッシュ)
ブラウザ側(クライアントサイド)に保存される一時的なキャッシュです。ユーザーがページ間を遷移した際、以前表示したページや、<Link> コンポーネントによってプリフェッチ(事前取得)されたページのRSCデータを一時的にブラウザ内に保持し、高速なページ切り替えを実現します。
- 消去方法:
Server Actions(
revalidatePathなど)が成功すると、クライアント側のこのキャッシュも自動的に無効化され、最新のデータがフェッチされます。
3. 実践:キャッシュを意図通りに制御するパターン
パターンA: 常に最新データを取得したい(キャッシュ不要)
リアルタイム性が求められるページ(ダッシュボードや株価など)では、fetch のオプションでキャッシュを明示的にオプトアウト(無効化)します。
const res = await fetch('https://api.example.com/realtime-data', {
cache: 'no-store'
});
あるいは、ファイルレベルで以下のようにエクスポートしてオプトアウトすることもできます。
export const dynamic = 'force-dynamic';
パターンB: オンデマンド再検証(revalidateTag)
管理画面からブログを更新した際など、特定のタグが割り振られたキャッシュだけをクリアしたい場合に利用します。
// fetch時にタグを付与しておく
const res = await fetch('https://api.example.com/posts', {
next: { tags: ['posts-list'] }
});
// Server Actionなどの中でキャッシュクリアを実行
import { revalidateTag } from 'next/cache';
async function updatePost() {
'use server';
await saveToDatabase();
// 'posts-list' のキャッシュを一括無効化
revalidateTag('posts-list');
}
まとめ
Next.js App Routerのキャッシュをうまく手懐けるための鍵は、「Request Memoization と Data Cache の寿命の違い」、そして**「動的関数の割り込みによる Full Route Cache のオプトアウト条件」** を正しく把握することです。
開発環境(next dev)では分かりづらいキャッシュの挙動も多いため、本番デプロイ前に必ず next build を実行し、どのページが静的(○)または動的(λ)としてコンパイルされたかを確認する習慣をつけましょう。
