updateTag vs revalidateTag comparison
updateTag is for Server Actions only with immediate cache expiration and read-your-own-writes use case. revalidateTag works in Server Actions and Route Handlers with stale-while-revalidate behavior and background refresh use case.
revalidatePath usage and recommendation
revalidatePath invalidates all cached data for a specific route path. Use it when you want to revalidate a route without knowing which tags are associated with it. Prefer tag-based revalidation (revalidateTag/updateTag) over path-based when possible — it is more precise and avoids over-invalidating.
revalidatePath usage example
import { revalidatePath } from 'next/cache'
export async function updateUser(id: string) {
// Mutate data
revalidatePath('/profile')
}
Use revalidatePath to invalidate all cached data for a specific route path.
Caching strategy for static content with CMS
When content doesn't need time-based revalidation (e.g., data from a CMS), use cacheTag and a long cacheLife like 'max' to keep it in the static shell. Configure the content source to trigger a webhook or other notification that calls revalidateTag when the content changes. This reduces unnecessary time-based revalidation for content that hasn't changed.
What to cache with use cache
Cache data that doesn't depend on runtime data and that you are OK serving from cache for a period of time. Use 'use cache' with cacheLife to describe that behavior.
In-memory cache persistence in serverless environments
In serverless environments, in-memory cache entries may not persist across revalidations. See runtime caching considerations for details.
cacheLife profiles and durations
cacheLife accepts profile names or custom configuration objects. Profiles are: default (stale 5m, revalidate 15m, expire never), seconds (stale 30s, revalidate 1s, expire 60s), minutes (stale 5m, revalidate 1m, expire 1h), hours (stale 5m, revalidate 1h, expire 1d), days (stale 5m, revalidate 1d, expire 1w), weeks (stale 5m, revalidate 1w, expire 30d), max (stale 5m, revalidate 30d, expire 1y).
cacheLife custom configuration object
For fine-grained control, cacheLife accepts an object with stale (duration in seconds until considered stale), revalidate (duration in seconds until revalidated), and expire (duration in seconds until expired) properties.
Short-lived caches excluded from prerenders
A cache is considered 'short-lived' when it uses the seconds profile, revalidate: 0, or expire under 5 minutes. Short-lived caches are automatically excluded from prerenders and become dynamic holes instead.
cacheLife usage example
import { cacheLife } from 'next/cache'
export async function getProducts() {
'use cache'
cacheLife('hours')
return db.query('SELECT * FROM products')
}
Use cacheLife inside a 'use cache' scope to set the cache lifetime.
cacheTag usage example
import { cacheTag } from 'next/cache'
export async function getProducts() {
'use cache'
cacheTag('products')
return db.query('SELECT * FROM products')
}
Use cacheTag inside a 'use cache' scope to tag cached data so it can be invalidated on-demand.
revalidateTag behavior and usage
revalidateTag invalidates cache entries by tag using stale-while-revalidate semantics — stale content is served immediately while fresh content loads in the background. This is ideal for content where a slight delay in updates is acceptable, like blog posts or product catalogs. Call revalidateTag in a Server Action or Route Handler.
revalidateTag with max duration example
import { revalidateTag } from 'next/cache'
export async function updateUser(id: string) {
// Mutate data
revalidateTag('user', 'max')
}
The second argument sets how long stale content can be served while fresh content generates in the background. Using 'max' gives the longest stale window.
updateTag behavior and limitations
updateTag immediately expires cached data for read-your-own-writes scenarios — the user sees their change right away instead of stale content. Unlike revalidateTag, updateTag can only be used in Server Actions.
updateTag usage example
import { updateTag } from 'next/cache'
import { redirect } from 'next/navigation'
export async function createPost(formData: FormData) {
const post = await db.post.create({
data: {
title: formData.get('title'),
content: formData.get('content'),
},
})
updateTag('posts')
redirect(`/posts/${post.id}`)
}
Use updateTag in Server Actions to immediately expire cache for user-visible changes.
Route Handlers caching behavior
Route Handlers are not cached by default. Only GET methods can be opted into caching using route config options such as export const dynamic = 'force-static'. Other supported HTTP methods are not cached, even if placed alongside a cached GET method in the same file.
GET Route Handler caching with dynamic config
export const dynamic = 'force-static'
export async function GET() {
const res = await fetch('https://data.mongodb-api.com/...', {
headers: {
'Content-Type': 'application/json',
'API-Key': process.env.DATA_API_KEY,
},
})
const data = await res.json()
return Response.json({ data })
}
Prerendering stops when accessing non-deterministic or runtime data
When Cache Components is enabled, GET Route Handlers are prerendered at build time if they don't access uncached or runtime data. Prerendering stops if the GET handler accesses network requests, database queries, async file system operations, request object properties (like req.url, request.headers, request.cookies, request.body), runtime APIs like cookies(), headers(), connection(), or non-deterministic operations like Math.random().
use cache in Route Handlers requires extraction to helper function
use cache cannot be used directly inside a Route Handler body; it must be extracted to a helper function. Cached responses revalidate according to cacheLife when a new request arrives.
Cached Route Handler example with use cache
import { cacheLife } from 'next/cache'
export async function GET() {
const products = await getProducts()
return Response.json(products)
}
async function getProducts() {
'use cache'
cacheLife('hours')
return await db.query('SELECT * FROM products')
}
Static prerendering example for Route Handler
export async function GET() {
return Response.json({
projectName: 'Next.js',
})
}
This Route Handler doesn't access uncached or runtime data, so it will be prerendered at build time.
Dynamic Route Handler example with runtime API
import { headers } from 'next/headers'
export async function GET() {
const headersList = await headers()
const userAgent = headersList.get('user-agent')
return Response.json({ userAgent })
}
This Route Handler accesses request-specific data via the headers() runtime API, so prerendering terminates and it runs at request time.
Metadata file types are cached by default
Special Route Handlers like sitemap.ts, opengraph-image.tsx, icon.tsx, and other metadata files are cached by default.
manifest.js is cached by default
manifest.js is a special Route Handler that is cached by default unless it uses a Request-time API or dynamic config option.
robots.js caching behavior
robots.js is a special Route Handler that is cached by default unless it uses a Request-time API or dynamic config option.
use cache directive for data-level caching
To cache an asynchronous function that fetches data, add the `use cache` directive at the top of the function body. Data-level caching is useful when the same data is used across multiple components, or when you want to cache the data independently from the UI. Example: `export async function getUsers() { 'use cache'; cacheLife('hours'); return db.query('SELECT * FROM users'); }`
use cache directive for UI-level caching
To cache an entire component, page, or layout, add the `use cache` directive at the top of the component or page body. If you add `use cache` at the top of a file, all exported functions in the file will be cached.
Cache keys include arguments and closed-over values
Arguments and any closed-over values from parent scopes automatically become part of the cache key, which means different inputs will produce separate cache entries.
Pair use cache with cacheLife
Every cache directive should be paired with a `cacheLife()` call. Without one, the implicit `default` profile applies.
use cache: private for runtime-dependent data
`use cache: private` gives a lifetime to a function that reads cookies, headers, or searchParams directly, so it can be included in a prefetch. This is an alternative to wrapping in Suspense.
Runtime caching for cached components gated behind request data
When a cached component is gated behind request data, it isn't added to the prerendered static shell. At runtime it's cached in-memory by default, which doesn't persist across serverless requests. Use `use cache: remote` for durable, shared caching.
Handling random values and timestamps in caching
Operations like `Math.random()`, `Date.now()`, or `crypto.randomUUID()` produce different values each time they execute. Cache Components requires explicit handling. `performance.now()` is meant for telemetry and Next.js doesn't treat it as a value to guard.
Cache random values to share across users
Alternatively, you can cache the result so all users see the same value until revalidation. Example: `export default async function Page() { 'use cache'; const buildId = crypto.randomUUID(); return <p>Build ID: {buildId}</p>; }`
Where cached content is stored
A cached function's output is serialized into an RSC payload at build time or runtime. Next.js can: render it to HTML and store on disk (static shell at build time, concrete page after ISR), keep it in a per-instance in-memory store (ephemeral on serverless by default), or use a durable cache handler (with use cache: remote), or send it to the browser (for client navigation or prefetch).
Prerendered HTML storage
The RSC payload is rendered to HTML and stored on disk when self-hosting, or in the platform's durable storage behind a CDN. That HTML is the static shell at build time and the concrete page after an ISR upgrade, with `revalidate` and `expire` controlling when it's rebuilt.
use cache: remote for shared durable caching
`use cache: remote` moves cached results to a durable cache handler shared across instances, a network roundtrip that pays off only at high hit rate. This is used instead of the default per-instance in-memory store.
Browser cache storage and stale window
The RSC payload is included in the RSC sent for client navigation or prefetch, where the browser keeps it fresh for its `stale` window. `use cache: private` results live only in the browser cache.
App Shell session-specific caching
An App Shell that reads cookies() or headers() is session-specific, cached per session on the client rather than in the shared server cache.
Cache key includes build id
All cache stores are scoped to a single deployment. A new deploy starts fresh, new prerenders are built, and use cache entries don't carry over, even durable remote ones, because the cache key includes the build id.
Cache Components with 'use cache' directive
Cache Components is a feature that enables component and function-level caching using the 'use cache' directive. It allows you to mix static, cached, and dynamic content within a single route by prerendering a static HTML shell that's served immediately, while dynamic content streams in when ready. Cache duration is configured with cacheLife(), cached data is tagged with cacheTag(), and on-demand invalidation is done with updateTag().
Client Cache storage and behavior
The Client Cache is an in-memory cache in the browser that stores RSC Payload for visited and prefetched routes. During client-side navigation, Next.js serves cached layouts and loading states instantly without a server request. Pages are not cached by default but are reused during browser back/forward navigation. The client cache is cleared on page refresh.
Client Cache invalidation methods
The client cache can be invalidated programmatically with revalidateTag, revalidatePath, updateTag, router.refresh, cookies.set, or cookies.delete. Client cache duration can be configured globally with staleTimes or per-route via the stale property in cacheLife (recommended).
Revalidation strategies time-based and on-demand
Revalidation is the process of updating cached data. It can be time-based using cacheLife() to set cache duration or on-demand using cacheTag() to tag data, then updateTag() to invalidate.
'use cache' directive syntax and scope
The 'use cache' directive marks a component or function as cacheable. It can be placed at the top of a file to indicate that all exports in the file are cacheable, or inline at the top of a function or component to mark that specific scope as cacheable.
force-cache option caches individual fetch requests
By default, fetch requests are not cached. To cache individual fetch requests, set the cache option to 'force-cache' in the fetch call.
unstable_cache wraps non-fetch async functions for caching
unstable_cache allows you to cache the result of database queries and other async functions that don't use fetch. It accepts three arguments: the async function to cache, an array of cache key prefixes, and a configuration object with optional tags and revalidate properties.
unstable_cache configuration options
The third argument to unstable_cache accepts: tags (array of strings for on-demand revalidation with revalidateTag), and revalidate (number of seconds before the cache is revalidated).
dynamic route segment config values
The dynamic export can be set to one of four values: 'auto' (default, caches as much as possible), 'force-dynamic' (renders for each user at request time), 'error' (forces prerendering and errors if any Request-time APIs are used), or 'force-static' (forces prerendering and returns empty values for cookies, headers, and useSearchParams).
force-dynamic is equivalent to setting cache: no-store on all fetch requests
Setting dynamic to 'force-dynamic' is equivalent to setting the option of every fetch() request to { cache: 'no-store', next: { revalidate: 0 } } and setting fetchCache to 'force-no-store'.
error dynamic mode forces prerendering with cached data
Setting dynamic to 'error' forces prerendering and causes an error if any components use Request-time APIs or uncached data. It is equivalent to setting every fetch() option to { cache: 'force-cache' } and setting fetchCache to 'only-cache'.
force-static mode with revalidation capability
Setting dynamic to 'force-static' forces cookies(), headers(), and useSearchParams() to return empty values while still allowing revalidation via revalidate(), revalidatePath(), or revalidateTag().
fetchCache advanced caching option
fetchCache allows overriding the default cache option for all fetch requests in a layout or page. Valid values are: 'auto' (default), 'default-cache', 'only-cache', 'force-cache', 'default-no-store', 'only-no-store', and 'force-no-store'.
fetchCache default behavior
By default, Next.js caches any fetch() requests that are reachable before any Request-time APIs are used and does not cache fetch requests discovered after Request-time APIs are used.
fetchCache 'default-cache' option
'default-cache' allows any cache option to be passed to fetch but if no option is provided, sets cache to 'force-cache', making even fetch requests after Request-time APIs considered static.
fetchCache 'force-cache' option
'force-cache' ensures all fetch requests opt into caching by setting the cache option of all fetch requests to 'force-cache'.
fetchCache 'default-no-store' option
'default-no-store' allows any cache option to be passed to fetch but if no option is provided, sets cache to 'no-store', making even fetch requests before Request-time APIs considered dynamic.
fetchCache 'only-no-store' option
'only-no-store' ensures all fetch requests opt out of caching by changing the default to cache: 'no-store' if no option is provided and causing an error if any fetch requests use cache: 'force-cache'.
fetchCache 'force-no-store' option
'force-no-store' ensures all fetch requests opt out of caching by setting cache to 'no-store' for all fetch requests, forcing re-fetch every request even if they provide 'force-cache' option.
force options override only options in fetchCache
When both 'only-cache' and 'force-cache' are provided, 'force-cache' wins. When both 'only-no-store' and 'force-no-store' are provided, 'force-no-store' wins. Force options change behavior across the route and prevent errors from only options.
fetchCache incompatible combinations not allowed
A combination of 'only-cache' and 'only-no-store' in a single route is not allowed. A combination of 'force-cache' and 'force-no-store' in a single route is not allowed.