The four Next.js caches — and why your data doesn't update
Four caches: Request Memoization (one render pass), Data Cache (server, survives deploys), Full Route Cache (static HTML+RSC, cleared on deploy), Router Cache (client). Diagnose stale data layer by layer; revalidatePath/Tag invalidate; Next 15 stops caching fetch by default.
A content team publishes a price change in the CMS on Friday afternoon. The on-call engineer checks production: old price. They redeploy — still the old price. Now it’s officially spooky: a fresh deploy is serving data that no longer exists anywhere. They add cache: 'no-store' to the fetch, redeploy again, and the page updates — but now every request hits the CMS and p95 latency triples. Monday’s postmortem finds three separate caches in the chain, each with a different lifetime: the Data Cache had stored the CMS response persistently — it survives deployments by design, the Full Route Cache had baked the stale data into static HTML at build time, and several users kept seeing the old price even after the server was fixed because their browser’s Router Cache held the previous payload. Every “fix” attacked one layer while another kept serving the stale bytes. Next.js caching isn’t one cache to bust; it’s four, stacked, with different stores, scopes, and invalidation rules — and you debug them in order.
The four layers, from innermost to closest-to-user
By the end of this section you’ll be able to name which cache is responsible for each stale-data symptom you encounter and pick the right invalidation lever instead of reaching for a blanket no-store.
Request Memoization is React, not infrastructure: identical fetch calls (same URL, same options, GET) made during a single server render pass execute once; the rest get the memoized result. Lifetime: one request — the memo table dies when the render finishes. This is why a layout and a page can both call getUser() without a double round-trip, and why you don’t thread props from layout to page. It only deduplicates within one render; it never serves anything to a second request. For non-fetch work (a direct DB query), React.cache() gives the same per-request memoization.
The Data Cache is the server-side store of fetch responses, keyed by URL plus options. Its defining — and most surprising — property: it is persistent across incoming requests and across deployments. Shipping new code does not clear it; the Friday price bug lives here. You control it per-fetch: next: { revalidate: 3600 } for time-based expiry, next: { tags: ['products'] } for on-demand purging with revalidateTag, cache: 'no-store' to bypass entirely. In Next 14 and earlier, fetch responses were cached here by default (force-cache); in Next 15 the default flipped to uncached — more on that below.
The Full Route Cache is the rendered output of statically rendered routes — the RSC payload plus HTML — produced at build time or in the background after a revalidation. It’s what makes a static route answer in single-digit milliseconds without running your components. Scope: server; lifetime: until revalidation, and unlike the Data Cache it is cleared on every deploy. Routes that use dynamic APIs — cookies(), headers(), reading searchParams in a page, or dynamic = 'force-dynamic' — opt out entirely and render per request.
The Router Cache lives in the browser’s memory: it stores the RSC payload of visited route segments for the session, so back/forward navigation is instant and prefetched links navigate without a server round trip. It is the only client-side layer, and the only one your server cannot directly flush — it empties on a hard reload, on router.refresh(), or when a server action calls revalidatePath/revalidateTag or sets a cookie.
// One fetch, three cache decisions:
const res = await fetch('https://cms.example.com/api/products/42', {
next: {
revalidate: 3600, // Data Cache: stale after 1h, refreshed in background
tags: ['products', 'product-42'], // Data Cache: purgeable on demand
},
});
// In the server action that edits the product:
import { revalidateTag } from 'next/cache';
export async function updateProduct(formData: FormData) {
'use server';
await db.product.update(/* ... */);
revalidateTag('product-42'); // purges Data Cache entries → Full Route Cache
// regenerates → client Router Cache invalidated
}Tradeoff: each layer trades freshness for a different cost. Memoization is free correctness. The Data Cache saves origin load but can serve data that no longer exists, across deploys. The Full Route Cache gives CDN-grade latency but only to routes that renounce per-request data. The Router Cache makes navigation feel native but means the server can be fixed while a user’s tab still shows the bug.
Invalidation: revalidate, tags, and opting out
Time-based revalidation (revalidate: 3600, or export const revalidate = 3600 at the segment level) works like stale-while-revalidate: the first request after expiry still gets the stale entry, the refetch happens in the background, and the next request sees fresh data. That “one more stale hit” surprises people who expect a hard TTL.
On-demand invalidation is the precise tool: revalidateTag('products') purges every Data Cache entry tagged with it and invalidates the Full Route Cache of routes built from it; revalidatePath('/products') does the same by route. Called inside a server action, they also invalidate the client’s Router Cache for affected paths, so the user who made the mutation sees the result immediately. Called from a route handler (say, a CMS webhook — the right fix for the Friday bug), they fix the server caches but cannot reach into browsers; open tabs catch up on their next hard navigation or refresh.
Opting out is per-layer: cache: 'no-store' skips the Data Cache for one fetch; export const dynamic = 'force-dynamic' makes the whole route render per request (skipping the Full Route Cache); using cookies() or headers() does the same implicitly. Note the asymmetry: an uncached fetch inside an otherwise static route makes the route dynamic — but the route being dynamic does not stop its fetches from using the Data Cache. Route caching and data caching are independent axes.
On Next 14, a marketing page fetches prices from a CMS with a plain fetch (no options). The CMS content changes, the team redeploys the site — and production still serves the old price. Why can a fresh deploy serve stale data?
Diagnosing “my data doesn’t update”, layer by layer
This is the most-filed Next.js bug in production, and it yields to an ordered walk — server first, client last.
Step 1 — is the route static? Check the build output: a circle marker means static, an ƒ means dynamic. If it’s static and your data must be per-request, the Full Route Cache is serving build-time HTML and no fetch option will save you — the route needs dynamic = 'force-dynamic' or a dynamic API. If it should be static but periodically fresh, set revalidate.
Step 2 — is the fetch cached? On Next 14, a bare fetch defaults to force-cache: your entry may be months old and deploy-proof. Decide its real freshness contract: a revalidate window, tags plus webhook-driven revalidateTag, or no-store if it’s genuinely per-request.
Step 3 — does the mutation invalidate? A server action that writes to the database but never calls revalidatePath/revalidateTag leaves every cache intact — the write succeeded, the caches just weren’t told. This is the single most common omission.
Step 4 — is it only stale in one browser tab? Server responds fresh via curl, but the user sees old data on back-navigation: that’s the Router Cache. If the staleness follows a mutation, the fix belongs in step 3 (actions invalidate the Router Cache too); for “data changed elsewhere” freshness, router.refresh() or accepting session-scoped staleness are the options.
Together these four steps mean you always have a culprit before you touch code: if it’s stale after deploy, it’s the Data Cache; if only one tab, it’s the Router Cache; if the route’s build output shows a circle, it’s the Full Route Cache. Without step 1, you can waste an hour adding no-store to a fetch inside a statically rendered route where no fetch ever runs per request.
The discipline is to name the layer before reaching for a fix. Sprinkling no-store everywhere “works” by collapsing all four layers — and triples your origin load, which is how the Friday hotfix took down p95.
Next 15 flipped the defaults — version your advice
Everything above describes mechanics common to both majors, but the defaults changed materially in Next 15, and half the advice on the internet silently assumes the wrong one. In Next 15: fetch is no longer cached by default (no-store semantics unless you opt in with cache: 'force-cache' or a revalidate option), GET route handlers are no longer cached by default, and the Router Cache no longer reuses page segments by default (staleTime of 0 for pages — a fresh navigation refetches the page, while back/forward restoration and layouts still come from the cache; tunable via the staleTimes config). The Full Route Cache and static rendering remain defaults for routes without dynamic APIs.
Practically: on a Next 15 codebase, the Friday bug as told mostly can’t happen with a bare fetch — but it returns the moment someone adds force-cache for performance and forgets the invalidation half of the deal. And teams upgrading from 14 see the mirror-image incident: origin traffic jumps after the upgrade because every formerly-default-cached fetch now hits the source per request. Before debugging any caching behavior, run next --version — the same code has different semantics across that boundary.
A user edits an item via a server action that writes to the DB and calls revalidatePath. They see fresh data. A colleague who already had the list page open in another browser still sees the old item after navigating back and forth in that tab. Which layer and why?
- 01Name the four caches with store, scope, lifetime, and how each is invalidated.
- 02Walk the 'my data doesn't update' diagnosis in order, and state what changed in Next 15.
Next.js stacks four caches with different stores, scopes, and lifetimes, and stale-data bugs are diagnosed by naming the layer before fixing it. Request Memoization deduplicates identical fetch calls within one server render pass — it’s why layouts and pages fetch the same data freely — and dies with the request. The Data Cache stores fetch responses server-side, keyed by URL and options, and persists across requests and deployments — the cache that lets a freshly deployed site serve data that no longer exists; it’s controlled per fetch with revalidate windows (stale-while-revalidate: one more stale hit after expiry), tags for on-demand purging, or no-store to bypass. The Full Route Cache is the build-time (or revalidation-time) RSC payload and HTML of statically rendered routes — cleared on deploy, skipped by any route using cookies(), headers(), or force-dynamic; route caching and data caching are independent axes. The Router Cache is the browser’s in-memory store of visited segments making back/forward instant — the one layer the server can’t flush remotely: server actions calling revalidatePath/revalidateTag (or setting cookies) invalidate it for the mutating user, while other open tabs stay stale until refresh. The production diagnosis runs server to client: check whether the route is static in the build output, whether the fetch is cached and under what contract, whether the mutation actually calls revalidatePath/revalidateTag (the most common omission), and only then whether the remaining staleness is one browser’s Router Cache. Blanket no-store collapses all layers and multiplies origin load — the hotfix that triples p95. Next 15 flipped the defaults: fetch and GET route handlers are uncached unless you opt in, and the Router Cache stops reusing page segments (staleTime 0, tunable via staleTimes), so the same code has different caching semantics across the 14/15 boundary — version-check before you debug, and expect upgraded apps to see origin traffic jump where default caching silently carried them. Now when you see stale data in production, you’ll name the layer first: deploy-proof staleness points at the Data Cache, one-tab staleness points at the Router Cache, and “circle in build output” points at the Full Route Cache — and each layer has exactly one right lever.
Practice
Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.
Something unclear?
Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.
Apply this
Put this lesson to work on a real build.