比較表
fetch cache | revalidatePath | revalidateTag | no-store | |
|---|---|---|---|---|
| 無効化の単位 | fetch リクエスト単位 | ルート(パス)単位 | タグ単位 | なし(毎回取得) |
| 更新トリガー | 時間(revalidate 秒数) | 手動(関数呼び出し) | 手動(関数呼び出し) | 常時 |
| 複数ページをまたぐ無効化 | 難しい | パスを個別に列挙 | タグ 1 つで一括 | 該当なし |
| Server Action との相性 | ○ | ◎ | ◎ | ○ |
| 実装の手軽さ | ◎(fetch オプションのみ) | ○ | △(fetch タグ設計が必要) | ◎ |
| リアルタイム性 | 低〜中 | 高(更新直後に反映) | 高(更新直後に反映) | 最高 |
各手段が向く場面
fetch cache(force-cache / next.revalidate)
// 60 秒ごとに再検証(ISR 相当)
const data = await fetch("/api/posts", { next: { revalidate: 60 } });
// キャッシュを強制(next.js 14 以前のデフォルト相当)
const data = await fetch("/api/posts", { cache: "force-cache" });
向く場面:
- ブログ記事・製品一覧など、頻繁には変わらないコンテンツ
- 更新頻度が低く、数分〜数時間の古さが許容できるデータ
Next.js 15 での変更: Next.js 15 からデフォルトが
no-store相当に変わりました。キャッシュを有効にするにはcache: "force-cache"またはnext.revalidateの明示が必要です。
revalidatePath
// Server Action でデータ更新後にパスのキャッシュを無効化
"use server";
import { revalidatePath } from "next/cache";
export async function updatePost(id: string, data: FormData) {
await db.post.update({ where: { id }, data: { ... } });
revalidatePath("/posts"); // 一覧を無効化
revalidatePath(`/posts/${id}`); // 詳細も無効化
}
向く場面:
- 管理画面・編集フォームで記事を更新したあとに一覧・詳細ページを即時反映させたいとき
- 更新対象のパスが明確で、列挙しやすいとき
revalidateTag
// fetch 側でタグを付けておく
const posts = await fetch("/api/posts", {
next: { tags: ["posts"] },
});
// 更新時にタグで一括無効化
import { revalidateTag } from "next/cache";
revalidateTag("posts"); // "posts" タグを持つ全 fetch キャッシュを無効化
向く場面:
- 1 つのデータが複数のページ(一覧・詳細・関連一覧など)で使われているとき
- CMS の Webhook で更新通知を受け取ってキャッシュを自動クリアするとき
revalidatePathでパスを列挙するのが煩雑になってきたとき
no-store
// このデータは常にキャッシュしない
const data = await fetch("/api/stock", { cache: "no-store" });
向く場面:
- 在庫数・リアルタイムスコアなどリアルタイム性が必須のデータ
- 認証後のユーザー固有データ(ユーザー A に B のデータを見せてはいけない場合)
- デバッグ中にキャッシュを完全に無効化したいとき
更新トリガーの違い
| キャッシュを無効化するタイミング | |
|---|---|
fetch cache(revalidate) | 設定した秒数が経過したあと、次のリクエスト時 |
revalidatePath | 関数を呼び出した瞬間(Server Action 完了後など) |
revalidateTag | 関数を呼び出した瞬間 |
no-store | 毎回のリクエストで取得(キャッシュしない) |
revalidatePath / revalidateTag はオンデマンド(手動トリガー)で、更新処理のあとに明示的に呼び出します。fetch cache の revalidate は時間ベースで自動的に再検証します。
ルート単位 / データ単位の違い
revalidatePath: URL パスに紐づくキャッシュをまとめて無効化する。同じページ上の複数 fetch をまとめて無効化できる反面、関係ないデータまで再取得させることがあるrevalidateTag: fetch リクエストにタグを付けておき、タグ単位で無効化する。影響範囲をデータの種類で制御できる。ただしfetch側のタグ設計が前提になるno-store: fetch リクエスト単位でキャッシュを完全に無効化する。ルートをまたぐ考慮が不要なシンプルさがある
よくある選び方
データが更新されたとき、即座に反映する必要があるか?
├── No → fetch cache(revalidate: N 秒で時間ベース再検証)
└── Yes
├── 更新対象ページが少なく列挙しやすいか?
│ └── Yes → revalidatePath
├── 複数ページにまたがるデータか?
│ └── Yes → revalidateTag(fetch タグを設計する)
└── リアルタイム性が必須 / ユーザー固有データか?
└── Yes → no-store
注意点
revalidatePath/revalidateTagは Server Action・Route Handler の内部でのみ呼べる。クライアントコンポーネントからは直接呼べないrevalidatePath('/', 'layout')は/レイアウト配下すべてを無効化するため影響範囲が広い。必要最小限のパスを指定するrevalidateTagはfetchにnext: { tags: [...] }を付けて初めて有効になる。タグなしのfetchはrevalidateTagの対象外- Next.js 15 では
fetchのデフォルトキャッシュ動作が変わった。cache: "force-cache"を明示しない限りキャッシュされない
関連サンプルの見どころ
- nextjs-cache-revalidation:
revalidatePathを Server Action から呼び出して一覧ページを即時更新するパターン
fetch cache オプションの詳細(force-cache / revalidate / tags)は → fetch cache
cache: 'no-store' の詳細と revalidate 系との違いは → cache: 'no-store'