Enabling the new caching model
Next.js 16 became generally available on October 21, 2025. Its release blog instructs developers to set `cacheComponents: true` in `next.config.ts` or `next.config.js` to enable Cache Components, which provides `use cache`, `cacheLife` and `cacheTag`.
The directive first appeared experimentally in Next.js 15.0 and became stable in 16.0.0. The accompanying `cacheLife` and `cacheTag` functions also stabilized in 16.0.0; their earlier `unstable_`-prefixed versions are deprecated.
The dev.to guide describes `use cache` as the replacement for three earlier mechanisms: `unstable_cache` wrappers, fetch-specific caching options such as `next.revalidate`, and implicit route caching. Previously, compiler inference and the presence of dynamic functions could determine whether a route was static or dynamic. An unconfigured fetch could remain cached indefinitely or not be cached at all, and behavior that appeared dynamic in development could produce static output serving stale data for hours in production.
The replacement puts cache policy alongside the operation being cached. Database queries, fetch calls and React Server Components use the same directive rather than separate wrappers or fetch options. Migration means removing those older controls and explicitly marking functions or components whose results should be cached.
What gets cached, and how keys are built
Place `'use cache'` at the beginning of an async function to cache the result of a database query, an API call or an expensive computation. Each distinct set of inputs gets its own entry: `getProduct('123')` and `getProduct('456')` do not share a result.
An async Server Component can use the same directive to cache its React Server Component (RSC) payload. While that entry remains usable, Next.js can return it without running the component body again.
Cache keys incorporate the function's identity, its arguments and values captured from surrounding scopes. A change to a captured value can therefore produce a different key even when the explicit arguments remain unchanged.
Arguments and results must be serializable. Supported values include primitives, plain objects, arrays and React elements. Returning a non-serializable value, such as a class instance with methods, causes a runtime error; cache the data extracted from that instance instead.
Plain `use cache` scopes cannot directly read `cookies()`, `headers()` or `searchParams`. Read request-specific data in a non-cached parent, resolve it there and pass the resulting value to the cached function as an argument.
Controlling freshness and invalidation
`cacheLife()` accepts a named profile or a custom object containing `revalidate`, `expire` and `stale`. These settings govern the refresh window, the hard expiration limit and the client-side lifetime, respectively.
Omitting `cacheLife()` selects the implicit `default` profile. Next.js documentation specifies a 5-minute client-side stale time, a 15-minute server-side revalidation window and no hard expiration based on time. The documentation recommends an explicit `cacheLife()` call in every cached scope so developers can see the lifetime where it applies.
The browser keeps server-cache content in memory according to the `stale` setting, but the client router imposes a minimum of 30 seconds even if a shorter duration is configured. The server communicates that lifetime through the `x-nextjs-stale-time` response header.
`cacheTag()` attaches labels that can later identify entries for invalidation. One call accepts no more than 128 tags, with a maximum of 256 characters per tag. Next.js skips overlong tags and drops any beyond the 128th, logging a console warning in either case. Reapplying a tag has no additional effect.
For stale-while-revalidate behavior, Next.js 16 requires a `cacheLife` profile as the second argument to `revalidateTag()`. The release blog recommends `'max'` for most cases, as in `revalidateTag('blog-posts', 'max')`. The older single-argument call, including `revalidateTag('products')` shown in the dev.to guide, still works but is deprecated.
Two new APIs are restricted to Server Actions. `updateTag()` expires data and immediately reads a fresh version within the same request, providing read-your-writes behavior for forms and user-triggered mutations. `refresh()` instead refreshes uncached dynamic data, such as notification counts or live metrics, without changing the cache.
Request-time data and build failures
Next.js 16 also provides `'use cache: private'` for functions that need to access `cookies()`, `headers()` or `searchParams` inside the cached scope. Unlike plain `use cache`, private results are not retained in a server cache across production requests. These functions execute at request time, are excluded from static shell generation and cannot use custom cache handlers. Neither directive permits `connection()`.
Passing unresolved request-time data into a plain `use cache` scope is not a workaround. According to the Next.js documentation, a scope that accesses Promises resolving to runtime or uncached data can leave prerendering waiting for data that cannot resolve during the build. After 50 seconds, the build times out with the error "Filling a cache during prerender timed out...".
That timeout can occur when `cookies()` or `searchParams` Promises arrive through props, captured variables or shared storage such as a Map. Direct calls to `cookies()` or `headers()` inside plain `use cache` fail immediately with a different error.
Choosing storage for production
The default function-level cache lives in each instance's memory. As the dev.to guide explains, servers and serverless instances do not share those entries, and cold starts do not preserve them.
For durable storage shared across instances, the guide points to `use cache: remote` and a custom cache handler configured in `next.config.ts`. Each remote cache hit adds a network round trip, so the guide recommends measuring the hit rate before switching. Its rule of thumb is that a rate below 80% likely leaves network overhead outweighing the benefit; the cost of the cached operation also matters.
Durable storage does not carry usable entries into a new deployment. Each deployment generates a new build ID that forms part of the cache key, making earlier entries inaccessible. The new deployment therefore starts with an empty cache even when a durable handler is in use.
Other requirements for the upgrade
Beyond caching, Next.js 16 requires at least Node.js 20.9.0 and TypeScript 5.1.0, according to the release blog. Node.js 18 is no longer supported.
The release also renames `middleware.ts` to `proxy.ts`, with the exported function renamed `proxy`. The older `middleware.ts` convention is deprecated but remains available for Edge runtime use cases.









