Caching has always been the most confusing part of the Next.js App Router. Next.js 15 changed the defaults, and Next.js 16 went further with Cache Components — and with Next.js 16.4 (October 2026) they're enabled by default in new projects created with create-next-app.
The examples below use the kind of content-heavy site I build most often — news portals and business sites. Here's the mental model that makes it click.
The one-sentence version
Everything is dynamic by default; you opt into caching explicitly with "use cache".
That's the opposite of the early App Router, where fetch was cached unless you opted out — which surprised a lot of people.
Turning it on
For existing apps, enable Cache Components in next.config:
// next.config.mjs
const nextConfig = {
cacheComponents: true,
};
export default nextConfig;
There's also a next upgrade --agent command in recent releases that gives AI coding agents version-specific migration guidance — useful if you're upgrading a large app.
"use cache": cache a component or function
// app/components/LatestNews.js
import { cacheLife, cacheTag } from 'next/cache';
export default async function LatestNews() {
'use cache';
cacheLife('minutes'); // how long it stays fresh
cacheTag('articles'); // label for on-demand invalidation
const res = await fetch('https://api.example.com/articles?limit=10');
const articles = await res.json();
return (
<ul>
{articles.map((a) => <li key={a.id}>{a.title}</li>)}
</ul>
);
}
You can put "use cache" at the top of a file, a component or any async function. The inputs (props/arguments) become part of the cache key automatically.
cacheLife: how long is "fresh"?
Built-in profiles like 'seconds', 'minutes', 'hours', 'days' and 'max' cover most needs. For a news site I use:
- homepage headlines →
'minutes' - article pages →
'hours'plus tag-based invalidation on edit - about/contact pages →
'max'
cacheTag + revalidation: update instantly when content changes
When an editor publishes or edits an article, you don't want to wait for a timer. Tag the cached data and invalidate the tag from a Server Action or route handler:
'use server';
import { revalidateTag } from 'next/cache';
export async function publishArticle(formData) {
await db.article.update(/* ... */);
revalidateTag('articles', 'max');
}
Next.js 16 also added updateTag for "read-your-own-writes" inside Server Actions — the user who just saved sees fresh data immediately. Check the docs for which one fits your case; the API surface has been moving quickly.
Dynamic parts: Suspense is the boundary
With Cache Components, anything that reads request data (cookies(), headers(), search params) is dynamic. Wrap it in <Suspense> so the static shell renders instantly and the personalised part streams in:
import { Suspense } from 'react';
export default function Page() {
return (
<>
<LatestNews /> {/* cached */}
<Suspense fallback={<p>Loading…</p>}>
<UserGreeting /> {/* reads cookies, dynamic */}
</Suspense>
</>
);
}
This is Partial Prerendering in practice: a static, cacheable shell with dynamic holes.
Instant Navigations (16.3+)
Next.js 16.3 introduced Instant Navigations — prefetching and caching that make server-rendered apps feel like single-page apps. Combined with Cache Components, most navigations on a well-structured site don't wait for the server at all.
Common mistakes to avoid
- Putting
"use cache"on something that reads cookies. User-specific data must not land in a shared cache. Keep it dynamic, behind Suspense. - Caching without tags. Then the only way to update is waiting for expiry. Tag anything editors can change.
- Huge cache keys. Passing an entire object as a prop to a cached component makes every render a cache miss. Pass IDs.
- Forgetting the CMS webhook. If content comes from a headless CMS, call
revalidateTagfrom a webhook route on publish.
Should you migrate now?
- New project? Yes — it's the default.
- Existing app on the old model? Upgrade incrementally, page by page. Start with your highest-traffic routes; that's where the performance and hosting-cost gains are.
- Security first: whatever you do, keep Next.js patched. Recent releases fixed several advisories, including an SSRF issue in image optimisation.
Key takeaways
- Next.js 16 is dynamic by default;
"use cache"opts in. - Use
cacheLifefor time-based freshness andcacheTag+ revalidation for content changes. - Put request-specific UI behind
<Suspense>so the static shell stays fast. - Never cache user-specific data in a shared cache.
