Revalidation (Time & On-Demand)
Revalidation is the process of telling Next.js that cached data (and any static HTML built from it) is no longer fresh and should be regenerated. There are two complementary ways to trigger it: on a fixed time interval, or on demand at the exact moment you know data changed.
Time-based revalidation, recap
Time-based revalidation is what powers ISR: you attach a revalidate window, in seconds, to a fetch call or a whole route segment, and Next.js treats the cached result as stale once that window elapses, regenerating it in the background on the next request.
// Per fetch call
await fetch('https://api.example.com/posts', { next: { revalidate: 3600 } })
// Per route segment
export const revalidate = 3600This is simple and effective as a baseline, but it is inherently imprecise — data might change one second after a regeneration just happened, and visitors would still see the old version for up to another full window.
On-demand revalidation: the precise alternative
On-demand revalidation lets you invalidate exactly the right cached data at exactly the right moment, instead of guessing at a time window. Next.js provides two functions for this, both imported from next/cache and typically called from inside a Server Action or a Route Handler right after a mutation succeeds.
'use server'
import { revalidatePath, revalidateTag } from 'next/cache'
export async function publishPost(slug: string) {
await savePostToDatabase(slug)
// Invalidate one specific path...
revalidatePath(`/blog/${slug}`)
// ...or invalidate every fetch anywhere tagged 'posts'
revalidateTag('posts')
}Time-based vs. on-demand
Approach | Precision | Effort to wire up | Best for |
|---|---|---|---|
Time-based (revalidate: N) | Data can be stale for up to N seconds | One config value | Content you don’t control the update moment for (external APIs, CMS webhooks you haven’t wired up yet) |
On-demand (revalidatePath / revalidateTag) | Immediate — stale for effectively zero time | Requires calling it from the mutation code path | Content updated through your own app (Server Actions, admin panels, CMS webhooks) |
Combining both approaches
These two mechanisms are not mutually exclusive, and most production apps use both together. A sensible default is a moderately long time-based revalidate window as a safety net, paired with on-demand revalidation for the paths you know exactly when to invalidate.
// Fetch: cached for up to an hour, but also invalidatable early by tag
await fetch('https://api.example.com/posts', {
next: { revalidate: 3600, tags: ['posts'] },
})
// Server Action: called right after an edit, invalidates immediately
'use server'
import { revalidateTag } from 'next/cache'
export async function editPost() {
await updatePostInDatabase()
revalidateTag('posts') // don't wait for the hour-long window
}