patterntypescriptnextjsModerate
ISR revalidation: time-based vs on-demand with revalidatePath/revalidateTag
Viewed 0 times
Next.js 13+ (App Router caching), revalidateTag added in Next.js 13.4
ISRrevalidatePathrevalidateTagon-demand revalidationnext revalidatecache tags
Error Messages
Problem
Static pages go stale after deployment but you don't want to rebuild the entire site. On-demand revalidation is needed when content changes in a CMS, but the Next.js cache API differs between Pages Router and App Router.
Solution
App Router: use fetch cache options and revalidatePath/revalidateTag. Pages Router: use res.revalidate().
// App Router: time-based revalidation
export default async function BlogPage() {
const posts = await fetch('https://cms.example.com/posts', {
next: { revalidate: 3600 },
}).then(r => r.json());
return <PostList posts={posts} />;
}
// App Router: tag-based revalidation
const posts = await fetch('https://cms.example.com/posts', {
next: { tags: ['posts'] },
}).then(r => r.json());
// On-demand: Route Handler
// app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from 'next/cache';
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
const secret = request.nextUrl.searchParams.get('secret');
if (secret !== process.env.REVALIDATION_SECRET) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
revalidateTag('posts');
revalidatePath('/blog');
return NextResponse.json({ revalidated: true });
}
// App Router: time-based revalidation
export default async function BlogPage() {
const posts = await fetch('https://cms.example.com/posts', {
next: { revalidate: 3600 },
}).then(r => r.json());
return <PostList posts={posts} />;
}
// App Router: tag-based revalidation
const posts = await fetch('https://cms.example.com/posts', {
next: { tags: ['posts'] },
}).then(r => r.json());
// On-demand: Route Handler
// app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from 'next/cache';
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
const secret = request.nextUrl.searchParams.get('secret');
if (secret !== process.env.REVALIDATION_SECRET) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
revalidateTag('posts');
revalidatePath('/blog');
return NextResponse.json({ revalidated: true });
}
Why
ISR (Incremental Static Regeneration) lets pages be statically generated but refreshed in the background after a specified interval. Tag-based revalidation is more precise — you can invalidate specific data across multiple pages at once.
Gotchas
- revalidatePath and revalidateTag only work in Server Actions and Route Handlers, not in component code
- revalidatePath('/blog') also revalidates all pages under /blog by default — use { type: 'page' } to be specific
- In Pages Router, getStaticProps returns { revalidate: N } seconds — different API
- fetch cache is per-request in development — ISR behavior is only visible in production builds
Code Snippets
Tag-based cache invalidation
// Tag fetch and invalidate later
const data = await fetch(url, { next: { tags: ['products'] } });
// In a Server Action after mutation:
revalidateTag('products');Context
When managing cache freshness for statically rendered pages in Next.js
Revisions (0)
No revisions yet.