Next.jsのData Cacheが原因でCMS更新が反映されない問題
技術スタック
以下の技術スタックでブログサイトを構築。
- Next.js(App Router)
- Vercel
- microCMS
記事の更新は microCMS 上で行い、Webhook を利用して Vercel に再ビルドを通知する構成を採用。
問題
microCMS 上で記事を更新すると、Webhook 経由で Vercel による再ビルドは正しく実行される。
しかし、ビルド後のサイトに変更が反映されないという問題が発生。。。
原因:Next.js の Data Cache
Next.js(App Router)では、サーバー側で fetch のレスポンスを永続的に保持する「Data Cache」が存在。
このキャッシュの特徴。
- 明示的に 再検証(Revalidate) されない限り、サーバーリクエストやデプロイをまたいで保持され続ける。
つまり、再ビルドされたとしても、キャッシュが削除されていなければ古いデータが表示され続けるという仕組み。
参考ドキュメント:
Next.js has a built-in Data Cache that persists the result of data fetches across incoming server requests and deployments.
The Data Cache is persistent across incoming requests and deployments unless you revalidate or opt-out.
解決策:On-demand Revalidation の導入
Data Cache を明示的に無効化する手段として、On-demand Revalidation を採用。
これは、CMS 側でコンテンツが更新されたタイミングで、Next.js の API 経由でキャッシュに付けたタグを指定し、該当キャッシュを削除する仕組み。
導入方針
-
fetch
にtags: ['blog']
を指定し、該当データに「blog」というキャッシュタグを付与する - CMS 側の Webhook から
/api/revalidate?tag=blog
にPOST
リクエストを送信する - API 側で
revalidateTag('blog')
を呼び出して、該当キャッシュを削除する
参考ドキュメント:
On-demand Revalidation: Revalidate data based on an event (e.g. form submission). On-demand revalidation can use a tag-based or path-based approach to revalidate groups of data at once. This is useful when you want to ensure the latest data is shown as soon as possible (e.g. when content from your headless CMS is updated).
実装例
データ取得関数
src/app/libs/microcms.ts
export const getBlogData = async (queries) => {
return await client.getAllContents({
endpoint: 'blog',
queries: {
fields: '',
...queries,
},
customRequestInit: {
next: {
tags: ['blog'],
},
},
});
};
-
tags: ['blog']
を指定することで、この fetch のレスポンスには「blog」タグが付与され、Data Cache に保存される -
revalidateTag('blog')
を呼び出すことで、このタグに紐づいたキャッシュを削除できる
再検証用 API
src/app/api/revalidate/route.ts
import { revalidateTag } from 'next/cache';
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request) {
const tag = request.nextUrl.searchParams.get('tag');
if (!tag) {
return NextResponse.json({ message: 'No tag provided' }, { status: 400 });
}
try {
revalidateTag(tag);
return NextResponse.json({ revalidated: true, now: Date.now() });
} catch (err) {
return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
}
}
- CMS 側の Webhook をこのエンドポイントに向けて設定することで、更新のたびに自動で該当キャッシュを削除できる
- キャッシュ削除後、次回のアクセス時には最新データが取得され、表示内容も更新される
実装の参考にした記事:
Discussion