TOOLS BOX/ガイド/Next.js キャッシュ設計
Concept

Next.js キャッシュ設計

fetch cache / revalidatePath / revalidateTag / no-store の違いを比較し、場面に応じたキャッシュ設計の選び分けを整理するガイド。

nextjscachefetchrevalidatePathrevalidateTagno-storeisrserver-component

どういう場面で使うか

  • ·fetch cache(force-cache): 静的コンテンツ・滅多に変わらないデータを高速に配信したいとき
  • ·revalidatePath: 編集・削除など特定の操作後に、該当ページのキャッシュを即時クリアしたいとき
  • ·revalidateTag: CMS 更新や複数ページにまたがるデータが変わったとき、タグで一括無効化したいとき
  • ·no-store: リアルタイム性が必要・常に最新データを返す必要があるとき

注意点 / Pitfalls

  • ·revalidatePath / revalidateTag は Server Action・Route Handler の中でのみ呼べる
  • ·revalidateTag を使うには fetch 側で tags オプションを設定しておく必要がある
  • ·no-store を多用するとキャッシュの恩恵がなくなりサーバー負荷が増加する
  • ·revalidatePath に 'layout' を渡すとそのレイアウト配下全体が無効化されるため影響範囲が広い

補足

Next.js 15 では fetch はデフォルトでキャッシュしない(no-store相当)に変更された。明示的に cache: 'force-cache' または next.revalidate を設定した場合にキャッシュが有効になる。

比較表

fetch cacherevalidatePathrevalidateTagno-store
無効化の単位fetch リクエスト単位ルート(パス)単位タグ単位なし(毎回取得)
更新トリガー時間(revalidate 秒数)手動(関数呼び出し)手動(関数呼び出し)常時
複数ページをまたぐ無効化難しいパスを個別に列挙タグ 1 つで一括該当なし
Server Action との相性
実装の手軽さ◎(fetch オプションのみ)△(fetch タグ設計が必要)
リアルタイム性低〜中高(更新直後に反映)高(更新直後に反映)最高

各手段が向く場面

fetch cacheforce-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 cacherevalidate時間ベースで自動的に再検証します。

ルート単位 / データ単位の違い

  • 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')/ レイアウト配下すべてを無効化するため影響範囲が広い。必要最小限のパスを指定する
  • revalidateTagfetchnext: { tags: [...] } を付けて初めて有効になる。タグなしの fetchrevalidateTag の対象外
  • 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'

関連ドキュメント

関連サンプル

同じテーマや技術スタックを使った実装例