skip to content

In a deployed Next.js App Router app, a user edits a record; reloading the list page shows the new value, but navigating back to that list with a <Link> still shows the old one. Which caching layer is holding the stale copy, and how would you confirm it?

level: seniorimportance: must knowfreq 55%

answer

  1. reload fresh, link stale
  2. that asymmetry is client-side only
  3. incognito window as the control test
  4. curl the route, compare payloads
  5. the write has to invalidate the client

basics

~20 s

The client-side Router Cache. It keeps the payload of already-visited route segments in browser memory, so a soft navigation renders from memory while a full reload discards it and goes to the server. Confirm by checking that other browsers and direct server responses are fresh.

solid answer

~50 s

That symptom points squarely at the **Router Cache** — the client-side layer that keeps payloads of segments the user has already visited in the browser's memory. A soft navigation via `<Link>` can be answered from that memory, while a reload throws the memory away and goes to the server, which is exactly the split you are seeing. Confirm it with three cheap checks: open the same page in a fresh incognito window (fresh, because that browser has no cached segments), ask a colleague whether they see the new value (they do — nothing server-side is stale), and hit the route directly with curl (new value in the payload). If any of those still shows the old record, the problem is on the server instead and you are looking at stored data or stored route output. The fix belongs on the client: the navigation after a mutation has to invalidate that cache rather than reuse it.

go deeper

for a junior

Know that the browser can render an already-visited page from memory, which is why a full reload sometimes shows newer data than clicking a link does.

for a middle

Explain why that asymmetry can only come from a client-side layer, and name the checks — incognito window, another user, a direct request — that separate client staleness from server staleness.

for a senior

Show the whole diagnosis: symptom to layer, layer to evidence, and the ownership point that a mutation is not complete until the layers that could serve the pre-write copy have been invalidated.

for a principal

Own the pattern rather than the incident — how mutation paths across the codebase consistently invalidate what they invalidate, and what staleness window each class of screen is actually allowed to have.

## Read the symptom first The useful skill here is not knowing the layers by name, it is mapping a symptom onto one. The symptom given has a very specific shape: **reload is fresh, soft navigation is stale.** A reload discards the page's JavaScript memory; a `<Link>` navigation does not. So whatever is holding the old value must be living in that JavaScript memory — the client Router Cache. No server-side layer can produce that asymmetry, because a reload and a soft navigation both reach the same server. ## What the Router Cache is doing As the user moves around, the browser keeps the rendered payload of route segments it has already visited (and ones it prefetched) in memory. That is what makes going "back" to a list feel instant: nothing is refetched, the stored payload is re-rendered. The trade is precisely the bug you are looking at — the user mutated data after that payload was captured, and the capture does not know it is out of date. How long a segment stays reusable is version-sensitive, and this is worth stating explicitly in an interview. Through Next.js 14 a dynamically rendered page segment stayed reusable for roughly 30 seconds; Next.js 15 changed the default reuse window for page segments to zero, so page segments are re-requested on navigation while layout segments continue to be reused. If you are debugging this on Next.js 15 or 16 and still see a stale page, the interesting question becomes what re-rendered the stale content — a reused layout segment, or a client-side navigation that never told the router anything had changed. ## The three-check confirmation Before you change anything, prove which side of the network the staleness lives on. 1. **Fresh incognito window.** A different browser profile has an empty Router Cache. If the record is correct there, the server is fine and the client is holding the copy. 2. **A second user or a second device.** Same logic, and it rules out anything account-specific. 3. **A direct request to the route.** `curl` the URL. If the response contains the new value, no server layer is stale. If all three are fresh, you have your answer. This is worth the minute it costs, because the instinct in a hurry is to start adding invalidation on the server for a problem that never left the browser. ## What the other outcomes would mean If the reload is *also* stale, you are on the server, and there are two candidates: - **Stored route output.** Only statically rendered routes have stored output, so check the build output, which labels each route as static or dynamic. If this list route was prerendered, the whole rendered page is being served from that stored output. - **Stored data.** If the route renders per request and still shows the old record, the data feeding it came from the persistent server data layer rather than from your database. That second distinction is a good one to volunteer unprompted: the two server layers are stacked, so a route that renders per request cannot be stale because of stored *output* — it can only be stale because of stored *input*. ## Why this specific bug is so common The mutation and the navigation are usually written by different people, or at least at different times. Someone writes the edit form, gets it saving correctly, and verifies it by reloading the page. Someone else — or the user — reaches the list by clicking a link instead. Every layer behaved as documented; the missing piece is that finishing a mutation has to tell the router that its captured payloads are no longer valid, rather than leaving the browser to reuse them. In practice that means the mutation path is responsible for invalidating client-held segments as part of completing, and a manual client-side refresh of the current route is the blunt instrument when you need it. The important interview point is the ownership statement: **a write is not finished when the server accepts it; it is finished when every layer that could still serve the pre-write copy has been told.** ## A note on what does not cause this Per-render memoization is never the culprit for staleness between two page views. It only collapses duplicate calls within a single render and is discarded immediately afterwards, so it cannot carry a value from one request to the next. Candidates sometimes reach for it because it is the layer they remember most recently; being able to rule it out in one sentence is a small but real signal.

  • Suppose the reload shows the old value too — what does that tell you?
    That the staleness is server-side, so the client layer is exonerated. Then you separate the two server layers: check whether the build labelled this route static, in which case stored rendered output is being served, or whether it renders per request, in which case only the persistent data layer can be holding the old record.
  • How would you distinguish stored route output from stored data when both live on the server?
    By whether the route was prerendered. Only statically rendered routes have stored output, and the build output labels each route static or dynamic. A route that renders per request has no stored output to serve, so a stale value there can only have come from the persistent data layer feeding it.
  • Could per-render memoization ever cause this symptom?
    No. Memoization only collapses identical fetches within a single render and is discarded the moment that render finishes, so it cannot carry a value from one page view to the next. Ruling it out in one sentence is worth doing, because it shows you know the layer's scope rather than just its name.

saying these in an interview costs you the question

  • Blames the server cache without testing a fresh browser
  • Says a hard reload clears every Next.js cache
  • Confuses per-render deduplication with cross-request staleness
  • Assumes a successful write invalidates the client automatically

context