Caching in Next.js 16: The Complete Mental Model

How Next.js 16 caching works with Cache Components, the use cache directive, cacheLife lifetimes, and the static shell that mixes cached and streamed content.

9 min read

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:

typescripttypescript
// next.config.ts
import type { NextConfig } from 'next'
 
const nextConfig: NextConfig = {
  cacheComponents: true,
}
 
export default nextConfig

This 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:

typescripttypescript
// 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.

  • stale is how long the client shows cached content without checking the server. The default is 5 minutes.
  • revalidate is how often the server regenerates content in the background. The default is 15 minutes.
  • expire is 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:

Profilestalerevalidateexpire
default5 minutes15 minutesnever
seconds30 seconds1 second1 minute
minutes5 minutes1 minute1 hour
hours5 minutes1 hour1 day
days5 minutes1 day1 week
weeks5 minutes1 week30 days
max5 minutes30 days1 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.

Shell, cache, and streaming on one request

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.

DirectiveServer cacheScopeReads cookies and headers
use cacheIn-memory or cache handlerShared across usersNo, pass as arguments
use cache: remoteRemote cache handlerShared across usersNo, pass as arguments
use cache: privateNonePer browserYes

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.

typescripttypescript
// 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_cache is 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

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.
RunePowered by Rune AI

Frequently Asked Questions

Does Next.js 16 cache data by default?

No. With Cache Components enabled, data fetching is dynamic by default. You opt in by adding the use cache directive and a cacheLife lifetime.

What is the difference between use cache and React cache()?

Next.js use cache persists results across requests and has a lifetime. React cache() only deduplicates calls within one server request and has no lifetime.

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.