Caching in Next.js
Understand the layers of caching Next.js applies automatically — Request Memoization, Data Cache, Full Route Cache, and Router Cache.
Why Next.js Caches Aggressively
Next.js caches by default at multiple levels so that repeated work — the same fetch call, the same rendered route — doesn't happen more often than necessary. This is a major source of its performance, but it's also the single biggest source of confusion for developers new to the framework, since data can appear "stuck" if you don't know which cache is involved.
The Four Caching Layers
| Cache | What It Stores | Lifetime | Where |
|---|---|---|---|
| Request Memoization | Identical fetch() calls during one render pass | A single render pass | Server |
| Data Cache | Results of fetch() calls | Persists across requests until revalidated | Server |
| Full Route Cache | Rendered HTML + payload for static routes | Persists until the next build or revalidation | Server |
| Router Cache | Visited route segments, for instant back/forward nav | Session-based, in the browser | Client |
For most debugging, the Data Cache is the one to think about first — it's what determines whether a given fetch() call reuses a stored result or genuinely goes to the network again.
Controlling the fetch Cache
| Option | Behavior |
|---|---|
| fetch(url) | Cached indefinitely by default (like SSG data) |
| fetch(url, { cache: "no-store" }) | Never cached — fetched fresh on every request (SSR-like) |
| fetch(url, { next: { revalidate: 60 } }) | Cached, but eligible to regenerate after 60 seconds (ISR-like) |
| fetch(url, { next: { tags: ["posts"] } }) | Cached and taggable, for on-demand invalidation with revalidateTag() |
// Always freshconst res = await fetch(url, { cache: 'no-store' });
// Cached, refreshed at most every 5 minutesconst res2 = await fetch(url, { next: { revalidate: 300 } });
// Cached and tagged for manual invalidationconst res3 = await fetch(url, { next: { tags: ['posts'] } });Opting Out at the Route Level
Beyond individual fetch calls, a whole route segment can opt out of static caching by exporting a dynamic config.
// app/dashboard/page.tsxexport const dynamic = 'force-dynamic'; // always render fresh, never cache the routeCommon Beginner Mistakes
Very often it's the Data Cache working as designed — check whether that fetch needs no-store, a revalidate interval, or a manual revalidateTag() call after a mutation.
This throws away the performance benefit caching provides — reserve it for data that genuinely must be fresh on every request.
FAQs
Briefly, by design — it's optimized for instant back/forward navigation. router.refresh() or a revalidation call clears it when you need guaranteed-fresh data.
Not as a single global switch — caching is controlled per fetch call and per route, which is more granular but means being deliberate about where you need it off.
Summary
Next.js's caching happens at four distinct layers, and most control comes from fetch options and route-level exports. Next, you'll learn how to explicitly invalidate cached data after a mutation, with revalidation.