Deep Dive 02 — The Four Caching Layers in Next.js
Deep Dive 02 — The Four Caching Layers in Next.js
Hey everyone! "Why is my data stale?" is probably the #1 Next.js production question ever asked. The honest answer is: Next.js doesn't have ONE cache — it has four, each solving a different problem, each with different rules for when it clears. Let's map every single one, precisely.
What we will cover:
- The four caches, at a glance
- 1. Request Memoization
- 2. Data Cache
- 3. Full Route Cache
- 4. Router Cache (client-side)
- How they interact — a real debugging walkthrough
The Four Caches — At a Glance
┌────────────────────────┬──────────┬──────────────┬───────────────────────┐ │ Cache │ Where │ Lifetime │ Purpose │ ├────────────────────────┼──────────┼──────────────┼───────────────────────┤ │ 1. Request Memoization │ Server │ One request │ Dedupe identical │ │ │ │ │ fetch() calls │ │ 2. Data Cache │ Server │ Persistent │ Cache fetch() RESULTS │ │ │ │ (until told │ across requests │ │ │ │ otherwise) │ │ │ 3. Full Route Cache │ Server │ Persistent │ Cache the RENDERED │ │ │ │ (build time) │ HTML + RSC payload │ │ 4. Router Cache │ Browser │ Session │ Cache RSC payloads for │ │ │ │ (few mins) │ client-side navigation │ └────────────────────────┴──────────┴──────────────┴───────────────────────┘
1. Request Memoization
Same URL, fetched TWICE in the same render pass:
====================================================
async function Header() {
const user = await fetch("/api/user"); // actual network call #1
...
}
async function Sidebar() {
const user = await fetch("/api/user"); // SAME url — deduplicated!
... // reuses call #1's result
}
// Both Header and Sidebar rendered in the SAME request →
// Next.js automatically merges identical fetch calls into ONE.
This is why you can safely call the same data-fetching function from multiple components without manually "lifting state up" to avoid duplicate requests — Next.js already handles it, but ONLY within a single request/render pass. It clears the instant that request finishes.
2. Data Cache
This is the cache controlled by fetch()'s cache/next.revalidate options
from Chapter 05:
============================================================================
fetch(url, { cache: "force-cache" }) → stored HERE, indefinitely
fetch(url, { next: { revalidate: 60 } }) → stored HERE, stale after 60s
fetch(url, { cache: "no-store" }) → NEVER stored here
Unlike Request Memoization, this PERSISTS across different
requests, different users, even server restarts (it can be
backed by persistent storage, not just memory).
This is why redeploying doesn't automatically fix "stale data" —
the Data Cache can outlive a single deploy unless you explicitly
revalidate it (revalidatePath / revalidateTag from Chapter 05).
3. Full Route Cache
This caches the RENDER OUTPUT of a whole route — the RSC Payload AND the HTML — not just the fetch results feeding into it. ===================================================================== If EVERY fetch in a route uses the Data Cache (static-friendly), Next.js can go one step further and cache the ENTIRE rendered route too — so it doesn't even need to re-run your component code on each request, just serve the pre-built result. This is what makes a route "static" in the sense from Chapter 06 (SSG/ISR) — the Full Route Cache IS the mechanism behind SSG. If ANY part of the route opts out (cache: "no-store", cookies(), headers(), etc.) → the WHOLE route is excluded from this cache, and becomes dynamic (re-rendered per request).
Key relationship to remember: ================================ Full Route Cache is BUILT FROM the Data Cache. A route can only be fully cached if ALL its data fetches are ALSO individually cacheable. One "no-store" fetch anywhere in the route disqualifies the entire route from the Full Route Cache.
4. Router Cache (Client-Side)
This is the ONLY one of the four that lives in the BROWSER, not the server. Covered briefly at the end of Deep Dive 01. ===================================================================== When you visit (or hover a <Link> to) a route, Next.js stores its RSC Payload in an in-memory client cache. Navigate back to it (via Link or back-button) → INSTANT, reused from memory, no server round-trip at all. Default lifetime: roughly 30 seconds for dynamic routes, 5 minutes for static ones (subject to change between versions — the point is: it's short-lived and automatic, not permanent). router.refresh() — from Chapter 03 — explicitly CLEARS this cache for the current route and re-fetches fresh data from the server. This is the client-side escape hatch.
5. A Real Debugging Walkthrough
"I updated data in my database, but the page still shows the OLD value!" ============================================================================= Ask, in order: 1. Did the fetch() that loads this data use cache: "force-cache" (the old default) or next.revalidate? → It's sitting in the DATA CACHE. Fix: call revalidatePath/revalidateTag after the write. 2. Is the WHOLE route statically cached (Full Route Cache)? → Even a correctly revalidated Data Cache entry won't show up until the route itself re-renders. revalidatePath handles BOTH layers together, which is why it's usually the right tool. 3. Did you just call revalidatePath on the server, but the BROWSER is still showing an old client-side Router Cache entry? → Navigate again, or call router.refresh() client-side. Most real "stale data" bugs are #1 or #2 — a missing revalidatePath/ revalidateTag call after a mutation. That's why Server Actions (Chapter 08) almost always end with a revalidate call.
Key Points to Remember
- Request Memoization — dedupes identical fetches within ONE request. Clears immediately after.
- Data Cache — persists fetch() RESULTS across requests, controlled by
cache/next.revalidateoptions - Full Route Cache — caches the entire rendered route (HTML + RSC payload); only applies if every fetch inside is itself cacheable
- Router Cache — the ONLY client-side layer; caches RSC payloads in the browser for instant back/forward and repeat navigation
revalidatePath/revalidateTaginvalidate the Data Cache AND the Full Route Cache together — the usual fix for stale data after a mutationrouter.refresh()is the client-side equivalent — clears the Router Cache and re-fetches for the current view
What's Next?
That's the internals covered — rendering AND caching, end to end. Next up: a bonus chapter of real interview questions on everything from this series, with model answers.
Keep coding, keep learning! See you in the next one!
Post a Comment