A long-open tab's form submissions start failing with not-found right after a deploy, and a reload fixes them. What happened?
answer
- the build mints the address, the server only answers the current set
- content-derived identifiers change on any edit
- reload fixes it, which points at the client half
- errors burst at deploy time and decay
- reloading after a failed submit can eat the input
basics
~20 sThe client posts a write to a handler address the build minted. The new release recomputed that address and no longer answers the old one, so a stale tab posts where nothing is mapped. Reloading downloads current addresses.
solid answer
~50 sWhen a form or a client-invoked write is handled by server-side code in the same route module, the build has to give that handler an address — a generated identifier, a route plus an action name, an entry in a manifest. The client bundle carries that address. A new build recomputes identifiers (they often derive from module content) and serves only the current set, so a tab loaded before the deploy posts to something the server no longer maps and gets a not-found or a generic client error. A reload downloads a client with current identifiers, which is why it "fixes" it. Treat it as skew, not as a bug in the form: keep the previous release's mappings answerable for a drain window, detect the unknown-handler response explicitly, and tell the user the page is out of date without discarding what they typed.
go deeper
Recall that server-side write handlers are reached through an address the build generates, and that a tab loaded before a deploy still holds the previous release's addresses.
Explain why the address changes — content-derived identifiers, a rebuilt routing manifest — and why the response is an unroutable not-found rather than an error from your own handler.
Diagnose it in production: correlate the error burst with the deploy, compare client and server build identifiers, then keep old mappings alive for a drain window and stop the reload from eating the user's input.
Weigh the cost of retaining old deployments against a cheap client-side contract: detect the unroutable response, preserve the submission, and make writes idempotent so any retry policy is safe.
## How a client-issued write reaches the server Meta-frameworks let server-only code that handles a write live beside the component that submits it. That convenience has to be paid for at build time: the server code cannot travel to the browser, so the build replaces it in the client bundle with a **reference** — an address the client can call. The address takes different forms across frameworks: - a generated identifier, frequently derived from the module's path and contents, posted to a shared endpoint; - a route URL plus a named action the route dispatches on; - an index into a build-time manifest that maps addresses to handlers. What they share is the property that matters: **the address is minted by the build, and the server only answers the addresses the current build minted.** ## Why the identifier moves If the address is content-derived, any edit to that module — or to something it imports, or a change in how the build chunks modules — produces a different one. Even where the address is a stable name, the routing table that resolves it belongs to the deployed release, and the deploy replaced that table. So the sequence is: 1. A tab loads at release N and keeps a client bundle containing address `A`. 2. Release N+1 ships. Its manifest contains `A'`; nothing maps `A` any more. 3. The user submits. The request goes to `A`. 4. The server finds no handler and answers not found — or whatever generic client error the framework produces for an unroutable write. A reload downloads the release N+1 bundle, which contains `A'`, and the submit works. That "reload fixes it" step is the tell: it points at the client half being stale rather than at the server rejecting the data. ## Why this is the worst case of skew, not the mildest | | Skewed read | Skewed write | |---|---|---| | When it fails | On navigation, before the user invests anything | After the user has typed and pressed submit | | What is lost | A view, until reload | The input, if the page reloads or the state is discarded | | How it looks | Broken or empty view | "Something went wrong" on a save | | Reload as a cure | Fully cures it | Cures the address, may destroy the input | The instinct to "just reload on error" is exactly wrong here without care: a blind reload after a failed submit throws away the very data the user was trying to save. ## Diagnosing it - **Correlate with deploy times.** Skewed-write errors appear in a burst starting at a deploy and decay as sessions turn over. A genuine handler bug does not decay. - **Tag the client's build identifier on every error report** and compare it with the serving release. Two different values is the diagnosis, with no reproduction needed. - **Reproduce deliberately**: load the app, deploy, submit without reloading. If it fails the same way, you have it. - **Check the status and the body.** An unroutable write usually returns not found rather than a validation error, and the response body is the framework's generic one rather than your handler's. ## What to do about it 1. **Keep the previous release answering.** Retain old handler mappings, or keep the previous deployment addressable, for at least a realistic session length — a drain window — so an old client's write still lands on code that understands it. 2. **Detect the condition explicitly.** A write that comes back unroutable is not a validation failure. Branch on it: show "this page is out of date, reload to continue", keep the form populated, and let the user re-submit after the reload. 3. **Preserve input before any reload.** Stash the pending submission in browser storage keyed to the form, restore it after the document loads, and clear it on success. 4. **Be careful with automatic retries.** Retrying an unroutable address is pointless; retrying *after* a reload is fine, but a generic retry wrapper that fires on every failed write risks duplicate submissions when the failure was something else. Make writes idempotent where you can — a client-generated request key the handler deduplicates on — so a retry is safe by construction. 5. **Reduce identifier churn where the framework allows it**, so an unrelated refactor does not invalidate every write address in the app at once. The judgement call for a team is how much of this to buy. Retaining old mappings costs operational complexity; preserving input and showing a clear message is cheap and removes the data loss, which is the part users actually feel.
- Why is not-found the typical status rather than a validation error?The request never reaches your handler. The routing layer looks up the address the client posted, finds nothing registered for it in the current build, and answers with its unroutable response — generic, and produced before any of your validation code runs. A validation error would mean the handler ran and rejected the data.
- When is retrying a failed write safe, and what makes it safe?Retrying an unroutable address is useless — only a reload helps. Retrying anything else is safe only if the write is idempotent: the client generates a request key, the handler records it, and a repeat with the same key returns the first result instead of applying the change twice.
- How would you distinguish this from a genuine regression in the handler introduced by the same release?Compare client and server build identifiers on the failing requests: skew shows two different values, a regression shows matching ones. Skew errors also start exactly at the deploy and decay as sessions end, while a regression stays flat, and reproduces on a freshly loaded page.
saying these in an interview costs you the question
- Diagnoses it as a validation or authentication failure in the handler
- Reloads immediately on a failed submit and discards the user's input
- Assumes write addresses are stable across builds
- Adds a blind retry wrapper to every write without idempotency
- Dismisses it because it cannot be reproduced on a freshly loaded page