Technology · Next.js
Next.js Caching and Revalidation
Understand ISR, revalidatePath, revalidateTag, and caching strategies for optimal performance.
TL;DR
- 01Use revalidate to set cache time and update pages periodically.
- 02Use revalidatePath to manually update specific pages.
- 03Use revalidateTag for tag-based cache invalidation.
Static Generation with Revalidation
- Set a revalidate time to cache pages and periodically rebuild.
export const revalidate = 3600; // Revalidate every hour export default async function Page() { const data = await fetch("https://api.example.com/data"); return <div>{data}</div>; } - Pages are cached and served statically until the time expires.
- When the time expires, the next request rebuilds the page on demand.
- Great for content that changes infrequently but needs eventual freshness.
- revalidate is measured in seconds.
On-Demand Revalidation with revalidatePath
- Manually revalidate specific paths when data changes.
// app/api/revalidate/route.ts import { revalidatePath } from "next/cache"; export async function POST() { revalidatePath("/blog/[slug]"); revalidatePath("/blog"); return { revalidated: true }; } - Call this endpoint after updating content in your CMS or database.
- Paths are immediately regenerated on the next request.
- Pass exact paths or patterns for multiple routes.
revalidatePath("/blog/[slug]", "page"); // All dynamic blog posts revalidatePath("/", "layout"); // Entire site using this layout - Use webhooks from your CMS to trigger revalidation automatically.
Tag-Based Revalidation
- Use tags to group related cache entries for bulk invalidation.
// app/page.tsx export default async function Page() { const res = await fetch("https://api.example.com/posts", { next: { tags: ["posts"] } }); const posts = await res.json(); return <div>{posts}</div>; } - Revalidate all requests with a specific tag at once.
// app/api/revalidate/route.ts import { revalidateTag } from "next/cache"; export async function POST(request) { const tag = request.nextUrl.searchParams.get("tag"); revalidateTag(tag); // "posts" return { revalidated: true }; } - Useful for invalidating multiple related pages together.
// Invalidate all blog-related content revalidateTag("blog");
Fetch Cache Control
- Configure fetch caching behavior for API calls.
// Cache for 1 hour const res = await fetch("https://api.example.com/data", { next: { revalidate: 3600 } }); // Never cache const res = await fetch("https://api.example.com/data", { cache: "no-store" }); // Cache indefinitely (default) const res = await fetch("https://api.example.com/data", { next: { revalidate: false } }); - Fetch requests are cached by default in server components.
- Set no-store to always fetch fresh data on every request.
- Combine with tags for flexible cache invalidation.
Caching Strategies
- Static generation with periodic revalidation for blog posts.
export const revalidate = 86400; // Daily export async function generateStaticParams() { const posts = await getPosts(); return posts.map(p => ({ slug: p.slug })); } export default async function Post({ params }) { const post = await getPost(params.slug); return <article>{post.content}</article>; } - Dynamic rendering with revalidateTag for content that updates frequently.
export default async function Dashboard() { const data = await fetch("https://api.example.com/data", { next: { tags: ["dashboard"] } }); return <div>{data}</div>; } - Use manual revalidation with webhooks from your CMS.
- Choose based on how often content changes and performance needs.
Tip: Use tags for related content so you can invalidate all affected pages at once when content changes in your CMS or database.
Warning: Set appropriate revalidate times for your use case — too short defeats caching benefits, too long means stale content for users.