After a route-handled write succeeds and the app lands on the list route, why might the list still show the old rows?
answer
- the write changed data, not the screen
- a redirect is not an instruction to recompute
- an earlier result was reused
- declare what the change invalidated
- parent layout reads get forgotten
basics
~20 sBecause the write changed the store but nothing told the route's read steps they were out of date, so the response reused a result computed before the change. A write has to mark what it invalidated, not just redirect.
solid answer
~50 sA screen is produced by the route chain's read steps, and a framework will happily reuse an earlier result for them — an already-computed route payload held for a client-side navigation, a prerendered document, or a stored read result. The write mutates the underlying data, but none of those copies knows that. If the handler simply redirects, the response can be assembled from what was already there, and the user sees the state from before their own submission, which is a uniquely confusing bug because they watched themselves cause the change. The fix is that the handler declares what its change invalidated as part of the same response, so the affected reads run again before that response is produced. On either transport the effect is the same: the response is built from reads that ran after the change rather than from a copy taken before it.
go deeper
Hold on to the core idea: a write changes stored data, and the page you land on is built from reads that may have been answered from something computed earlier.
Explain that the redirect is a navigation instruction, not a recomputation instruction, and that the handler has to declare what its change invalidated so the affected reads run again for that same response.
Show the diagnosis: which copy answered — client-held payload, stored read result, prerendered document — and how you tell them apart. Name the parent-layout read as the one teams miss.
Talk about the cost curve. Blanket invalidation trades submit latency and origin load for safety; precise declaration needs conventions so every handler does it. Decide which default the team lives with and how it is enforced.
## The screen is not the data It is easy to think of a write as "changing the screen". It does not. It changes a record somewhere, and the screen is a separate artefact produced by the route chain's read steps — one per matched segment, each returning a value the segment renders. Between one render and the next, a framework has several legitimate reasons to reuse an earlier result rather than compute a new one: - a **route payload already computed** for this URL and kept for a fast client-side navigation; - a **document prerendered ahead of time**, served as a file; - a **stored result of a read** that was marked reusable for some window; - a copy held **in the browser tab** by the client-side router or a data layer. Each exists for a good reason and each is, the moment the write lands, potentially wrong. ## Why the redirect alone does not fix it Redirecting after a successful write is the right shape for history, but it is not an instruction to recompute anything. The browser follows the `Location` header and asks for the target. That request is then answered however the framework would normally answer it — including from any of the reusable copies above. Nothing in the redirect says "and the thing you had is stale", because the redirect is a statement about navigation, not about data. This is what produces the signature bug: **the user submits, is redirected, and lands on a page showing the world as it was before they submitted.** Refreshing a moment later often fixes it, which is exactly why it survives review — the developer testing it reloads out of habit. ## What a correct handler does The handler is the only code that knows what changed, so it is the only place that can say so. The shape is: 1. Perform the write. 2. Declare what it invalidated — the reads whose results are now wrong. 3. Return the outcome: a redirect, or a value to render. Steps 2 and 3 belong to the same response. The point is not merely that something is eventually purged; it is that **the reads feeding the very response the user is about to see run again**. A framework that handles this well treats the write and the render that follows it as one unit of work: the write happens, the marked reads re-run, the resulting document or payload is what the user receives. | Situation | What the user sees without the declaration | With it | |---|---|---| | Redirect to a list after creating a row | the list as it was before | the new row present | | Re-render the same route after an edit | the old values back in the form | the saved values | | Client-side navigation to a cached route | the copy the tab already held | a re-fetched payload | ## Which reads actually have to re-run Not all of them, and picking well is the skill. Three groups: - **The reads on the route the response will render.** These are non-negotiable; they are what the user is about to look at. - **Reads in the parent chain.** A layout showing a count, a total, or a badge is a read too, and it is the one people forget because it is not the page they were editing. - **Reads on other routes the change touched.** These matter for the next navigation rather than this response, and how far that purge reaches — across other routes, shared stores and open tabs — is a larger subject in its own right. Over-declaring is not free: mark too much and every write re-runs work nobody needed, which shows up as latency on the submit path. Under-declaring is the stale screen. The honest answer in an interview is that the handler should be explicit about what it touched rather than reaching for the blunt option by default. ## Diagnosing it When a screen is stale after a write, the question is *which copy answered*: 1. **Does a hard reload fix it?** If yes, something on the client answered and the server was fine. 2. **Does the scriptless path show it?** Submit with the enhanced path unavailable; if the fresh document is correct, the problem is on the client side of the handoff. 3. **Is a second request even made?** If the navigation after the write produced no request for the route's data, the payload came from something already held. 4. **Is the document identical byte for byte across two users?** That points at a shared prerendered or stored copy rather than a per-user one. ## Where frameworks differ The vocabulary varies — some frameworks re-run the route chain's reads automatically for any response produced by a write and require an explicit declaration only for *other* routes; others reuse aggressively and require the handler to say so in every case. Some address invalidation by path, some by labels attached when the read happened. What does not vary is the underlying obligation: a write that changes what a screen shows has to cause the screen's reads to run again, and the only code that knows which those are is the handler.
- Why does the bug so often survive local development?Developers reload constantly, and a reload usually bypasses whichever copy answered. Reusable copies are also frequently disabled or short-lived in a dev server, so the exact conditions that produce the stale screen only exist in a built, deployed run.
- Is it acceptable to invalidate everything after every write?It works and it is tempting, but it makes every submission pay for recomputing pages nobody asked for, and it hides which data a write actually touches. Prefer declaring the reads the change affects, and keep the blunt option for writes whose reach is genuinely wide.
- Which read is most often missed?The one in a parent layout — a counter, a total, a notification badge. It sits above the route being edited, it renders on every page, and it is nobody's mental model of "the thing I just changed", so it keeps showing the pre-write number.
saying these in an interview costs you the question
- Assumes redirecting after a write guarantees fresh data
- Adds a short delay before redirecting to let things settle
- Says the reload fixes it, so it is not a real bug
- Only refreshes the edited component and leaves layout reads stale
- Invalidates everything on every write and calls it correctness