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.
Tips
- 01Use tags for related content so you can invalidate all affected pages at once when content changes in your CMS or database.
Warnings
- 01Set appropriate revalidate times for your use case — too short defeats caching benefits, too long means stale content for users.
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.
FAQ
revalidatePath invalidates the cache for a specific URL or route segment, while revalidateTag invalidates all cached entries associated with a given tag across multiple routes. Use revalidateTag when a single content update (like a blog post edit) should purge several pages at once.
Call revalidatePath('/your-route') or revalidateTag('your-tag') inside a Route Handler (app/api/revalidate/route.ts). Protect the endpoint with a secret token by checking a query param or header against an environment variable before revalidating.
Pass { cache: 'no-store' } to fetch to always fetch fresh data, or { next: { revalidate: 0 } } for the same effect. For per-request caching without a time limit use { cache: 'force-cache' }, which is the default for static routes.
Yes — pass the interpolated path like revalidatePath('/posts/123') to purge a single dynamic page, or revalidatePath('/posts/[id]', 'page') to purge all pages matching that segment pattern at once.
generateStaticParams pre-builds specific dynamic routes at build time, while the revalidate option (set via fetch or route config) controls how long those static pages are served before being regenerated in the background. You typically combine both: generateStaticParams to build the initial set and revalidate to keep them fresh after deployment.