What Cache Components Actually Is
Next.js 16 Cache Components is an explicit, opt-in caching primitive for Server Components and async functions. Instead of the old Next.js 13–15 model — where fetches were cached by default and you fought to opt out — nothing is cached unless you add use cache. The unstable_ prefix dropped in 16.2. It is now the stable API.
The mental model shift is real. "Cache by default" to "dynamic by default, cache where you choose."
The Two Things You Enable
In next.config.ts, set the flag:
import type { NextConfig } from 'next'
const config: NextConfig = {
cacheComponents: true,
// Turbopack is now default in 16 — no separate flag needed
}
export default config
Without cacheComponents: true, the use cache directive is a no-op. Enable it once, then mark what you want cached.
Marking What Gets Cached
Add 'use cache' at the top of an async function or Server Component. Pair it with cacheLife for TTL and cacheTag for targeted invalidation.
import { cacheLife, cacheTag } from 'next/cache'
async function getServicesList() {
'use cache'
cacheLife('hours') // built-in: minutes | hours | days | weeks | max
cacheTag('services')
const res = await fetch('https://api.example.com/services')
return res.json()
}
To invalidate on demand — say, after a CMS webhook fires — call updateTag in a Server Action or Route Handler:
import { updateTag } from 'next/cache'
export async function POST() {
updateTag('services')
return new Response('revalidated')
}
That's the whole pattern. No revalidatePath, no revalidateTag from next/cache v1 — those still work for Page Router compatibility but updateTag is the Cache Components equivalent.
The Static Shell + Streaming Model
Next.js prerenders a static HTML shell immediately. Dynamic Server Components — anything without use cache — stream in via Suspense. This means your Lighthouse score stops being punished by a single slow data fetch buried in the page.
On a marketing site I rebuilt in Next.js 16, the hero, nav, and footer are pure static shell — delivered in milliseconds from Vercel's edge. The live pricing section, which hits a Postgres query via Prisma, streams in behind a <Suspense> boundary. TTFB on the static content is under 60 ms. The dynamic piece loads when it loads. Users see content immediately.
That outcome was not easy to achieve in Next.js 15 without careful no-store gymnastics. Here it is the default behavior.
What Broke During Migration
A few things bit me moving 15 → 16.
Fetch caching is gone from the fetch layer. In Next.js 15, fetch calls inside Server Components had their own cache by default (or with next: { revalidate }). In 16, that layer is removed. If you were relying on it and you migrate without adding use cache to your data functions, those calls run on every request. The site works, but it is slower. I found this by watching Neon query counts spike after a deploy.
unstable_cache wrappers need replacing. Any unstable_cache from Next.js 15 should become an async function with 'use cache'. The old API still works during transition but is deprecated.
Nested use cache scope. If a parent component has 'use cache' and a child does not, the child renders at cache time, not request time. That caught me once with a timestamp I had left in a child component — it was frozen at build/cache time. Mark dynamic leaves explicitly; wrap them in Suspense if they need request-time data.
Turbopack Default
Turbopack is now stable and the default dev server in Next.js 16. Cold starts on the marketing sites I moved dropped noticeably. Nothing to configure — it just works, unless you pass --no-turbopack to opt out.
When Opt-In Caching Hurts You
Honest tradeoff: opt-in caching means you can ship something slow. If a developer on the team forgets use cache on a heavy data function, that function runs on every request in production. There is no automatic fallback.
Mitigations I use: annotate data-fetching functions with 'use cache' as a discipline by default, review Vercel function logs after deploys, and run a quick Lighthouse check on any page with new Server Components before merging.
cacheLife tuning also matters. The built-in presets (minutes, hours, days) cover most cases, but for content that changes on a webhook (CMS-driven copy, pricing) I use short TTLs plus cacheTag/updateTag rather than relying on time-based expiry.
FAQ
What does use cache do in Next.js 16?
It marks an async function, Server Component, or file as cacheable. The return value is stored and reused until its cacheLife expires or a matching cacheTag is invalidated with updateTag. It only works when cacheComponents is enabled in next.config.
Does use cache work in Client Components?
No. It is Server-side only. Client Components still use standard React state and SWR/React Query patterns.
Can I use cacheTag with Prisma queries?
Yes. Wrap the query in an async function, add 'use cache', set your tag. Call updateTag('your-tag') from a Route Handler when the data changes.
Is cacheComponents flag required in production?
Yes. Without it, 'use cache' does nothing regardless of environment.
