The old Next.js caching was powerful and nearly impossible to reason about. Cache Components trade implicit magic for explicit intent — and that is the upgrade that matters.
TL;DR
- Next.js 16 introduces Cache Components, built on Partial Pre-Rendering (PPR) and an explicit
use cache. - Caching becomes opt-in and visible instead of implicit and surprising.
- One page can mix instantly-served static shells with dynamic, per-request holes.
- The win is a model you can actually hold in your head.
The problem with the old model
For a few versions, Next.js caching was infamous. Multiple overlapping caches, implicit defaults that changed between releases, and behavior that depended on subtle signals meant developers routinely could not answer a basic question: is this cached or not? The power was real; the predictability was not.
What Cache Components change
The new model inverts the default: instead of caching happening implicitly and you fighting to opt out, you mark what should be cached, explicitly.
async function ProductDetails({ id }: { id: string }) {
'use cache'
const product = await getProduct(id) // this result is cached
return <Details product={product} />
}
The use cache directive says, plainly, "this is cacheable." No directive, no caching. The behavior is visible in the code rather than inferred from framework defaults.
The best thing about explicit caching is not performance. It is that you can read a component and know what it does.
Partial Pre-Rendering: static shell, dynamic holes
Cache Components build on PPR, which lets a single page be partly static and partly dynamic. The static shell — layout, navigation, anything cacheable — is pre-rendered and served instantly. The dynamic parts — the user's cart, personalized content — stream in as holes within that shell.
┌─────────────────────────────┐
│ Static shell (instant) │
│ ┌───────────────────────┐ │
│ │ Dynamic hole (streams)│ │ ← per-request: user, cart, etc.
│ └───────────────────────┘ │
└─────────────────────────────┘
You no longer choose between "fully static and fast but impersonal" or "fully dynamic and personal but slow." You get both on the same page.
How to think about it
Three questions to ask per piece of UI:
- Is it the same for everyone? → cache it; it belongs in the static shell.
- Is it per-user or per-request? → leave it dynamic; it becomes a streamed hole.
- Is it expensive but shared? →
use cachewith an appropriate lifetime.
Migrating
If you are coming from the older caching model, the migration is mostly deletion — removing the workarounds you built to defeat implicit caching — followed by explicitly marking what genuinely should be cached. Many teams find their mental overhead drops sharply even before the performance wins land.
The takeaway
Cache Components are less a new feature than a correction: caching you opt into, can see, and can reason about. After years of "why is this stale / why is this not cached," a model that is explicit and local is exactly the right trade. Reach for use cache deliberately, lean on PPR for the static-shell-plus-dynamic-holes pattern, and caching stops being the scariest part of your Next.js app.