Caching in Next.js 16 is opt in. Data fetching is dynamic by default, and you cache deliberately with the use cache directive, which is part of the Cache Components feature. This article assumes that model, enabled with cacheComponents: true in next.config.ts.
The config alone does nothing until you mark a function to cache. Start with the feature flag:
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
cacheComponents: true,
}
export default nextConfigThis single flag turns on Cache Components, which enables the directive, Partial Prerendering, and automatic React Activity state preservation. A cached data function then looks like this:
// app/lib/posts.ts
import { cacheLife } from 'next/cache'
export async function getPosts() {
'use cache'
cacheLife('hours')
const res = await fetch('https://api.example.com/posts')
return res.json()
}The function runs once, stores its result, and reuses it for the next hour. cacheLife sets how long the entry stays fresh, and the use cache directive marks what to cache.
Why caching is opt in now
Under Cache Components, nothing is cached until you say so. A plain fetch call does not cache by default. Next.js 14 cached fetch results automatically, Next.js 15 dropped that default, and Next.js 16 finishes the shift by making every cache a deliberate choice.
In practice you cache shared content and stream the rest. A product page might cache product details but stream the visitor's cart, because the cart depends on who is asking.
What cacheLife controls
The cacheLife function assigns a lifetime to a cached scope using three properties.
staleis how long the client shows cached content without checking the server. The default is 5 minutes.revalidateis how often the server regenerates content in the background. The default is 15 minutes.expireis the maximum age before the next request must wait for fresh content. The default is that it never expires.
Next.js ships preset profiles that combine these three values:
| Profile | stale | revalidate | expire |
|---|---|---|---|
| default | 5 minutes | 15 minutes | never |
| seconds | 30 seconds | 1 second | 1 minute |
| minutes | 5 minutes | 1 minute | 1 hour |
| hours | 5 minutes | 1 hour | 1 day |
| days | 5 minutes | 1 day | 1 week |
| weeks | 5 minutes | 1 week | 30 days |
| max | 5 minutes | 30 days | 1 year |
Pass a profile name such as cacheLife('hours'), or pass an inline object for one-off timings. The full API is in cacheLife explained.
The static shell and streaming
Prerendering produces a static shell: the HTML for the parts of the page that are known at build time. Cached output joins the shell, and Suspense fallbacks mark where uncached content will stream in.
The browser gets the shell immediately, then the uncached piece arrives when it is ready. This is Partial Prerendering, and with Cache Components it is the default rather than a separate opt-in.
Cached output can live in three places.
- Prerendered HTML is stored on disk or in durable platform storage behind a CDN.
- The server cache holds results in memory per instance by default, so serverless instances often miss between requests.
- The browser keeps a copy for client navigation within the stale window.
The three directives
Cache Components ships three directives for different jobs.
| Directive | Server cache | Scope | Reads cookies and headers |
|---|---|---|---|
| use cache | In-memory or cache handler | Shared across users | No, pass as arguments |
| use cache: remote | Remote cache handler | Shared across users | No, pass as arguments |
| use cache: private | None | Per browser | Yes |
use cache is the default. The remote variant moves results to a durable cache handler shared across instances, which pays off at high traffic. The private variant never stores results on the server and keeps them in the browser only.
Revalidating cached data
Lifetimes expire on a timer, but you also need to invalidate on demand when data changes. Tag an entry with cacheTag, then purge it after a mutation.
// app/lib/posts.ts
import { cacheTag } from 'next/cache'
export async function getPosts() {
'use cache'
cacheTag('posts')
const res = await fetch('https://api.example.com/posts')
return res.json()
}After a write, call revalidateTag to refresh in the background or updateTag inside a Server Function for read-your-own-writes. To invalidate by route instead, revalidate the path directly.
The other cache layers
Next.js use cache is not the only cache in play, and the names are easy to blur.
- Next.js use cache persists results across requests and is part of Cache Components.
- React
cache()deduplicates calls within one request and has no lifetime. unstable_cacheis the older API for non-fetch functions, replaced by use cache in Next.js 16.
See use cache vs React cache vs unstable_cache for the full comparison.
What caching does not do
A cached scope cannot read request data. Accessing cookies or headers inside one fails, so read them outside the scope and pass values in as arguments. Random values such as Math.random() and Date.now() also need explicit handling.
For the exact failure mode and fixes, see why cookies and headers cannot be used inside use cache.
Rune AI
Key Insights
- Caching is opt in through cacheComponents and use cache.
- cacheLife sets stale, revalidate, and expire timings.
- Cached output joins the static shell while uncached content streams.
- use cache, use cache: remote, and use cache: private serve different scopes.
- use cache, React cache(), and unstable_cache are different layers.
Frequently Asked Questions
Does Next.js 16 cache data by default?
What is the difference between use cache and React cache()?
Conclusion
Next.js 16 caching is opt in. Enable Cache Components, mark shared work with use cache, give it a cacheLife lifetime, and stream everything request-specific behind Suspense. Revalidate by time or by tag, and keep use cache, React cache(), and unstable_cache separate.
More in this topic
`generateMetadata` Explained with Real Examples
What generateMetadata does, when it runs, and how to use it for real routes: awaited params, deduplicated data fetching, extending parent metadata, and returning a 404 from metadata.
Canonical URLs in Next.js: `metadataBase`, `alternates.canonical`, and Dynamic Pages
How canonical URLs work in the Next.js App Router: setting metadataBase once, writing alternates.canonical per route, handling dynamic segments, and what happens when the base URL is missing.
Open Graph and Twitter Card Metadata in Next.js
How to write Open Graph and Twitter card metadata in the Next.js App Router: the openGraph and twitter fields, automatic card defaults, article tags, and image merge rules.