Caching is only safe if you can update it the moment data changes.
Cached data needs a way to become fresh again. Next.js offers two strategies.
Time-based: the cacheLife profile decides when entries go stale and are regenerated in the background (stale-while-revalidate), and when they expire completely.
On-demand, after a mutation:
- `updateTag(tag)` expires tagged entries immediately so the user who made the change sees it on the next render (read-your-own-writes). It can only be called in Server Actions.
- `revalidateTag(tag, profile)` marks entries stale with stale-while-revalidate semantics: visitors may get the old version once while a fresh one is generated. In Next.js 16 the second argument is required;
'max'is recommended. Usable in Server Actions and Route Handlers, e.g. a CMS webhook. - `revalidatePath('/blog')` invalidates everything for a path (add
'layout'to include nested routes). Simple, but coarser than tags. - `refresh()` re-renders the current page's uncached server data from a Server Action; it doesn't touch tagged caches.
Choosing: user edits their own profile, use updateTag in the action. Editor publishes in a CMS, the webhook route calls revalidateTag('posts', 'max'). Tag precisely (post:42 as well as posts) so you can invalidate narrowly.
All caches are scoped to one deployment: a new deploy starts with fresh prerenders.
Example
// app/posts/actions.ts
'use server'
import { updateTag } from 'next/cache'
import { db } from '@/lib/db'
export async function renamePost(id: string, title: string) {
await db.post.update({ where: { id }, data: { title } })
updateTag(`post:${id}`) // the author sees the new title immediately
updateTag('posts')
}
// app/api/cms-webhook/route.ts
import { revalidateTag } from 'next/cache'
export async function POST(request: Request) {
if (request.headers.get('x-webhook-secret') !== process.env.CMS_SECRET) {
return new Response('Forbidden', { status: 403 })
}
const { postId } = await request.json()
revalidateTag(`post:${postId}`, 'max') // serve stale once, regenerate in background
revalidateTag('posts', 'max')
return Response.json({ revalidated: true })
}Remember
cacheLifehandles time-based freshnessupdateTag(Server Actions only) for read-your-own-writes;revalidateTag(tag, 'max')for background refreshrevalidatePathinvalidates by route;refresh()re-renders uncached data
Watch out for
- Calling
revalidateTag('posts')without the second argument, which Next.js 16 requires.