How revalidation works in Next.js
A deep dive into how Next.js revalidates cached content, including the tag system, cache consistency, and multi-instance coordination.
The Caching page covers how to use use cache, cacheTag, and cacheLife. This page explains how revalidation works internally, for platform engineers and advanced users who need to understand the system to implement custom cache handlers or debug revalidation behavior.
The Revalidation Model
Most routes in Next.js can be revalidated on demand. This includes App Router routes and Pages Router routes that produce ISR/prerender cache entries. Pages Router routes that are automatically statically optimized (pure static output) are not revalidated on demand. The ability to update cached content without redeploying is a core part of Next.js's rendering model.
There are two types of revalidation:
- Time-based revalidation uses a stale-while-revalidate pattern. The cached content is served immediately, and a background regeneration is triggered when the content's age exceeds the
cacheLifeorrevalidateduration. The stale content continues to be served until the fresh content is ready. - On-demand revalidation explicitly invalidates cached content by calling
revalidateTag()orrevalidatePath(). The next request to that content triggers a fresh render.
Good to know: Pages Router on-demand ISR APIs (for example res.revalidate() and the x-prerender-revalidate flow) are still supported and use the server cache handler (cacheHandler, singular). The cacheHandlers option (plural) is for 'use cache' directives.
What Gets Revalidated
When a route is revalidated, Next.js regenerates both the HTML response and the RSC payload (React Server Components payload) from the same React component tree. Both artifacts are stored together in the same cache entry.
This consistency matters because the RSC payload is used for client-side navigations. Browser navigations and client-side navigations should hold the same content.
What happens if they get out of sync
If a platform's cache serves HTML from one render and an RSC payload from a different render, users may see stale or mismatched content during client-side navigation. The primary mitigation is to cache HTML and RSC responses together with the same TTL and invalidation policy, and to respect the Vary header that Next.js sets. See CDN Caching for details.
A separate but related problem is cross-deployment skew: during rolling deployments, a client built with deploy A may receive responses from a server running deploy B. deploymentId mitigates this: when the client detects a different deployment ID from the server, it triggers a hard navigation to fetch consistent content.
Tag System Architecture
Next.js uses a tag-based system to track which cached content needs to be invalidated. There are two types of tags:
Explicit tags
Explicit tags are set by the developer using cacheTag() inside a use cache function, or via next: { tags: [...] } on a fetch call. When revalidateTag('my-tag') is called, all cache entries with that tag are invalidated.
Soft tags
Soft tags are automatically generated by Next.js based on the route path, prefixed with _N_T_. For example, the route /blog/hello generates soft tags like _N_T_/layout, _N_T_/blog/layout, _N_T_/blog/hello/layout, and _N_T_/blog/hello. Each segment in the path gets a layout tag, plus the leaf route itself.
Soft tags enable revalidatePath() to work through the same tag-based system. When revalidatePath('/blog/hello') is called, it invalidates cache entries associated with that path's leaf route tag and its ancestor layout soft tags (for example _N_T_/layout, _N_T_/blog/layout, _N_T_/blog/hello/layout, and _N_T_/blog/hello).
In the cache handler API, soft tags are passed to the get() method as the softTags parameter. Your handler should check whether any soft tag has been invalidated after the cache entry's timestamp. The getExpiration() method returns the most recent revalidation timestamp across all provided tags, or 0 if none have been revalidated. Your handler should treat an entry as stale if the returned timestamp is newer than the entry's own timestamp. See the cache handler API reference for the full semantics.
Multi-Instance Considerations
When running multiple Next.js instances behind a load balancer, revalidation events are local by default. Calling revalidateTag() on instance A only invalidates the cache on that instance. Other instances continue serving the stale content until they learn about the invalidation.
The cache handler API provides two hooks for distributed coordination:
updateTags()is called whenrevalidateTag()is invoked. Your handler should write the invalidation event to shared storage (for example, Redis or a database) so other instances can discover it.refreshTags()is called periodically, but always before starting a new request. Your handler should check shared storage for recent invalidation events and update its local tag state accordingly.
For implementation details and a Redis example, see Custom Cache Handlers.
Implementation Patterns for Platforms
Single instance
The default file-system cache handles consistency automatically. Cache writes are atomic on the local filesystem, and tag state is maintained in memory. No additional configuration is needed.
Multi-instance with shared cache
Without coordination, each instance independently serves content and handles revalidation using only its local cache. Different users may see different content depending on which instance serves their request, and on-demand revalidation only takes effect on the instance that received the call.
To reduce this window and ensure revalidation propagates across instances:
- Store tag invalidation timestamps in a shared service (Redis, DynamoDB, or a simple HTTP API).
- Implement
updateTags()to write to the shared service. - Implement
refreshTags()to read from the shared service. Your handler must catch errors inrefreshTags(): if it throws, the exception propagates as a request failure. Catching the error allows requests to continue with the last known local tag state, serving potentially stale content until connectivity is restored. - Store cache entries (HTML + RSC payload) in shared storage. Atomic writes reduce the mismatch window further but are not required for correctness.
CDN integration
If a CDN caches Next.js responses, it should respect the Vary header and the Cache-Control directives that Next.js sets. Do not cache HTML and RSC payload responses separately with different TTLs. See CDN Caching for details.
Graceful Degradation
The revalidation system prioritizes availability over strict consistency. Content is always served, even when infrastructure guarantees cannot be fully met:
- Cache write failure: the response is still served to the user because writes are asynchronous. The cache entry is lost, and the next request triggers a fresh render.
- Cache read failure: your handler should catch internal errors and return
undefined(the cache miss signal). The route is then server-rendered fresh. The framework does not wrapget()in a try/catch, so unhandled exceptions will propagate as render errors. - HTML/RSC cache inconsistency: if a CDN caches HTML and RSC responses with different TTLs or invalidation timing, users may see mismatched content during client-side navigation. Cache them together and respect the
Varyheader to avoid this. - Cross-deployment skew: during rolling deployments, configure
deploymentIdso that a build ID change triggers a hard navigation to fetch consistent content.
Cache failures result in degraded performance (stale content, extra renders), not broken applications.