Next.js の revalidatePath / revalidateTag が効かず古いデータが残る
App Router のキャッシュは fetch / Route / Router Cache の多層構造。
revalidatePath / revalidateTag は fetch と Route Cache を破棄するが、クライアント側 Router Cache や呼び出し場所の制約を踏まえないと反映されない。
#nextjs#revalidate#cache#app-router#isr
公開:
要約
revalidatePath / revalidateTag を呼んだのにページが更新されない場合、原因は App Router のキャッシュ階層を 1 つだけ叩いているケースが多い。
fetch にタグが付いているか、Server Action から呼ばれているか、クライアント Router Cache をどう更新するかの 3 点を順に確認する。
よくある原因
- タグ未指定:
fetch(url)のようにnext: { tags: [...] }を渡していないと、revalidateTag('posts')を呼んでも対応する fetch は破棄されない。 - Client Component から呼んでいる: 両 API は Server Action / Route Handler 限定で、Client Component から直接 import しても実行できない。
- Pages Router で使っている:
revalidatePathは App Router 専用。
Pages Router の ISR はres.revalidate('/path')を使う。 - クライアント Router Cache: サーバ側のキャッシュは破棄できても、クライアントが保持する Router Cache に古いデータが残り、ナビゲーションでは更新されないことがある。
解決策
1. fetch にタグを付ける
// app/lib/posts.ts
export async function getPosts() {
const res = await fetch("https://api.example.com/posts", {
next: { tags: ["posts"] },
});
return res.json();
}その上で Server Action 側で呼ぶ。
"use server";
import { revalidateTag } from "next/cache";
export async function createPost(formData: FormData) {
// ... 書き込み
revalidateTag("posts");
}revalidateTag 公式リファレンス のとおり、タグが一致した fetch だけが破棄される。
2. Server Action / Route Handler 内で呼ぶ
'use client' のコンポーネントから直接 revalidatePath を import しても動かない。
必ず 'use server' 関数の中で呼び、フォームの action などから起動する。
3. Pages Router は別 API
// pages/api/revalidate.ts
export default async function handler(req, res) {
await res.revalidate("/blog/" + req.query.slug);
res.json({ revalidated: true });
}App Router 用の API は pages/ 配下では使えない。
4. クライアント側の即時反映
"use client";
import { useRouter } from "next/navigation";
export function RefreshButton() {
const router = useRouter();
return <button onClick={() => router.refresh()}>更新</button>;
}router.refresh() は現在のルートを再 fetch して Router Cache を更新する。
App Router のキャッシュ階層は Caching in Next.js 公式ガイド で全体像が掴める。