A lazily loaded route's bundle request 404s for a user whose tab has been open since before a deploy. What happened, and how should the app recover?
answer
- the document outlived its build
- old filenames, new deployment
- not a render error, a missing artefact
- retry must re-request, not re-render
- reload once, with a guard
basics
~20 sThe open document carries the old build's bundle filenames, and the deploy removed those files, so the loader's request misses. Recover at a route-level failure path: retry once or twice, then reload the document once, guarded so it cannot loop.
solid answer
~50 sBundle filenames are usually derived from content, so a new build produces new names. A document loaded before the deploy still asks for the old ones, and if the deploy deleted them the loader's request fails — often an HTML error page, not a module. Nothing in the app is wrong: matching, params and the navigation all worked. Two points shape recovery. This is not a render error — the component never ran — so re-rendering the failed subtree changes nothing; a retry must call the loader again and issue a new request, which is why a rejection is not memoised. And the causes differ: a transient network failure deserves a bounded retry, while a removed file will never succeed for this document, so the fix is one full reload, guarded by a marker so a broken build cannot loop. Better still, keep the previous release's files served for a while.
go deeper
Understand the shape: the page in the tab was built against files that no longer exist on the server, so fetching a screen's code fails until the page is reloaded.
Explain why a retry, not a re-render, is the only thing that can help, and why a rejected loader must not be cached if a retry is to do anything.
Separate transient failure from a removed artefact, describe the bounded retry plus guarded single reload, and never leave the user on a blank screen.
Move the problem upstream: artefact retention across releases, deploy ordering, a version-change prompt, and a monitored failure rate that tells you when retention is too short.
## Why the request misses Builds usually publish bundle files under content-derived filenames, so a new build produces new filenames. A document that was loaded before a deploy carries the **old** build's filenames inside it. Its route table's loaders will ask for those exact files. If the deploy removed them, the request fails — a 404, or whatever the host serves for an unknown path, which may even be an HTML error page rather than a module. Nothing in the app is broken. The route table matched correctly, the params parsed, the navigation started; the only failure is that the artefact this document was built against no longer exists on the server. That is why the symptom is so specific: only users with long-open tabs see it, only on routes they had not yet visited in that tab, and it disappears the moment they reload. ## A load rejection is not a render error The component never ran. There is no bad state inside it to reset and no render to retry, so anything that merely re-renders the failed subtree changes nothing. Two consequences: - The recovery belongs at the route level, around the thing that owns the loader, not inside the screen that failed to appear. - A retry must **call the loader again** and issue a new request. If the router memoised the rejection, a retry is a no-op that replays the same failure — which is exactly why a resolved module is cached and a rejected one normally is not. ## Transient failure versus a removed artefact These look identical at the point of failure and need opposite responses. | | Transient network failure | Artefact removed by a deploy | |---|---|---| | Cause | offline, flaky link, timeout, server blip | the document references filenames that no longer exist | | Will a retry help? | yes, often on the first attempt | never, for this document | | Right response | retry, with a short backoff and a small cap | reload the document once, so the new build is fetched | | Wrong response | reload, losing the user's page state | retry forever and call it resilience | | Distinguishing hint | intermittent, may succeed on retry | deterministic for this tab, fine in a fresh one | You cannot always tell them apart from the failure alone, so the usual shape is: retry a small number of times first, and treat persistent failure as a possible stale document. ## The reload-once recovery 1. Catch the rejection at the route-level failure path. 2. Retry the loader once or twice with a short delay. If it succeeds, continue as normal; the user saw a brief pending state. 3. If it still fails, check a marker recorded for this document or session that says "a recovery reload has already been attempted for this URL". 4. If the marker is absent, set it and reload the document, ideally at the URL the user was navigating to so the reload lands where they wanted to go. 5. If the marker is present, do **not** reload. Show a failure state with an explicit retry the user controls, and report the failure. Step 5 is the whole point of the guard. Without it, a build that is genuinely broken for everyone turns into a reload loop: load, fail, reload, fail. A loop is worse than an error message, because the user cannot read anything and your own reporting drowns. ## Shrinking the window instead of only handling it - Keep the previous release's bundle files served for a while after a deploy rather than deleting them immediately. Old tabs then continue to work, and the failure becomes rare enough to be a genuine edge case. - Have the app notice that a new version is available and invite the user to reload at a moment of their choosing, so the stale document is replaced before it hits a missing file. - Deploy the new files before the document that references them, and remove old files last, so there is never a moment where a served document names a file that is not there. - Be aware that any local code cache the app itself owns can hold a stale document long after the server has moved on; that cache's own invalidation is a separate concern with its own rules. ## What the user should see Never a blank screen. Either the previous screen with a failure message and a retry control, or the route's failure state naming what happened in plain terms — something went wrong loading this page, try again — with the underlying cause reported for you rather than shown to them. And instrument it: a rising count of bundle-load failures immediately after each deploy is the signal that your artefact retention window is too short, which is a far better fix than better error copy.
- Why does re-rendering the failed route, or resetting its boundary, not fix this?Because the component never ran; there is no bad state to reset. The failure was a network request for a module. Recovery has to call the loader again so a new request goes out, which also means the router must not have memoised the rejection — otherwise the retry replays the stored failure.
- What stops a reload-based recovery from becoming a loop?A marker recorded for the document or session before reloading. On the next attempt, if the marker is present the app shows a failure state with a user-controlled retry instead of reloading again. Without it, a build that is genuinely broken for everyone cycles load, fail, reload — worse than an error message, because nobody can read anything.
- How would you make this failure rare rather than merely handled?Keep the previous release's bundle files served for a while after each deploy, publish new files before the document that references them, and remove old ones last. Optionally have the app notice a new version and invite a reload at a convenient moment. Then watch bundle-load failures per deploy: a spike means the retention window is too short.
saying these in an interview costs you the question
- Retries a removed file indefinitely and calls it resilience
- Reloads the document on any bundle failure, with no guard against a loop
- Thinks re-rendering the failed route re-requests the missing module
- Treats the 404 as a bug in the route table or the pattern
- Considers telling users to hard-refresh an adequate recovery
- Deletes the previous release's files the moment a deploy finishes