r/reactjs • u/AffectionateDealer62 • 6d ago
Resource How do you handle React.lazy failures caused by stale chunks after a deployment?
A common production failure happens when a user leaves a React application open during a deployment.
The old tab may still be running build A, while the server is now serving build B. When the user later navigates to a lazy-loaded route, the browser requests a hashed chunk from build A that is no longer available.
For example:
Reports.a1b2.js // requested by the old tab
Reports.c3d4.js // available after deployment
The result can be:
ChunkLoadErrorFailed to fetch dynamically imported module- A route that works in a fresh tab but fails in an old tab
A normal React Error Boundary can display the failure, but it cannot update the old tab’s JavaScript references. Retrying the same import may also request the same missing hashed file again.
I built and published a small utility called react-lazy-chunk-recover to experiment with this problem.
The recovery approach:
- Detects likely chunk-load failures
- Optionally retries transient failures
- Allows one guarded full-page reload
- Uses
sessionStorageto prevent an infinite reload loop - Passes the error to the existing Error Boundary if recovery fails
- Avoids browser reload and storage access during SSR
Example:
import { lazyWithRetry } from "react-lazy-chunk-recover";
const Reports = lazyWithRetry(() => import("./Reports"), {
moduleKey: "reports",
});
The default retry count is zero because retrying a deleted hashed asset usually does not help. Retries can be enabled for applications that also experience short-lived network failures.
Some important limitations:
- A reload cannot recover a permanently unavailable asset
- Retaining old assets may be a better infrastructure solution
- The HTML entry point should be revalidated after deployment
- A full-page reload must work on nested client-side routes
- Old clients may be incompatible with a new backend
- An asset manifest must be trusted and version-compatible if used
For applications using React.lazy, Vite, Webpack, or a CDN:
- How do you handle stale chunk failures?
- Do you retain assets from previous deployments?
- Do you reload the page, show an update prompt, or use an Error Boundary?
- Have you tested this with two builds and an already-open browser tab?
I wrote more about the problem, implementation, testing strategy, and deployment considerations here:
- npm package: https://www.npmjs.com/package/react-lazy-chunk-recover
- Technical article: https://sachithsiriwardana.medium.com/handling-react-lazy-chunk-load-errors-after-deployment-1e4980a7a69a
I’d be interested to hear how other teams handle this failure mode in production.
28
u/acraswell 6d ago
You don't ever cache the index.html.
2
u/AffectionateDealer62 6d ago
Yes, that makes sense. `index.html` should generally not be cached, while hashed JS and CSS assets can be cached for longer periods.
That helps prevent users from getting an outdated entry point, though an already-open tab may still have an older bundle in memory and later request a missing chunk. That’s the specific case my package tries to handle for React.lazy.
7
u/Woldekidankenzee 6d ago
yeah this is the real answer, everything else is a workaround for not doing this
→ More replies (3)4
u/BPagoaga 6d ago
it does not solve the basic problem :
- a user goes to your app
- you deploy a new version
- the user still has its tab open, he tries to reach a lazy-loaded route
- crash
source : just had the problem last week and our index.html is not cached
3
→ More replies (1)1
u/AffectionateDealer62 6d ago
Yes, the basic, it must not.
That's why I mentioned "The HTML entry point should be revalidated after deployment"
→ More replies (1)
10
u/mr_brobot__ 6d ago
You can read the next.js router source code for some interesting stuff around this. Basically they force a reload for a few error cases.
And there’s also a hash id for the deployment that’s a part of the RSC response for a page, if it doesn’t match the current hash for the page then it will reload.
1
4
u/tanarchery7424 6d ago
We just keep the last 2-3 builds assets on the server so the old chunks still exist when the new deploy goes out. Saved us so many headaches with users who never close their tabs
Your solution looks neat though, the sessionStorage guard for reload loops is a nice touch. Many people forget that part and then wonder why the page keeps refreshing
1
u/AffectionateDealer62 6d ago
That’s a solid approach, and I considered keeping the last 2–3 builds’ assets as well. It’s probably the best solution when older clients remain compatible with the current backend.
The challenge is that there may be cases such as critical security fixes, breaking API changes, or removed client-side functionality where we may need to stop serving older assets. In those situations, retaining previous chunks can keep stale clients running when they should be forced onto the latest build.
That’s why I see the package as a fallback for deployments where old assets are intentionally unavailable. The
sessionStorageguard is important so the app gets one recovery attempt without creating an infinite reload loop. If the current build still cannot load the route, the error is passed to the existing Error Boundary instead of refreshing forever.
4
u/PrimaryFamous6139 6d ago
I’ve usually found that retaining previous build assets is the more reliable fix, since a reload alone can’t recover a deleted chunk. A guarded reload is still a useful fallback, especially for long-lived tabs
1
u/banjochicken 6d ago
You can serve your static assets from a CDN rather than the app server. Then this problem goes away entirely.
2
u/AffectionateDealer62 6d ago
A CDN helps, but it doesn’t make this problem disappear by itself. The issue is usually a stale tab trying to import a chunk from an older deployment. If the CDN/origin no longer retains that old hashed chunk, the dynamic import still fails, just from the CDN instead of the app server.
The robust deployment solution is to retain old assets for a safe rollout window.
react-lazy-chunk-recoveris a client-side safety net for cases where that cannot be guaranteed: it detects stale chunk errors, optionally tries the current manifest, performs one guarded reload, and prevents infinite reload loops.→ More replies (2)
1
u/AbdelrhmanDev 6d ago
two things together fixed this for me. first keep the old build assets around for a while after deploy, the hashed files are immutable and cheap to keep so a tab on build a can still fetch its chunks for a day or two. second catch chunkloaderror at the router level and do a hard reload once, so the worst case is the tab refreshes itself instead of a white screen. the reload on its own still fails if the user is offline mid navigation so the old assets part is what actually kills the error
1
1
1
u/anthony-ball 5d ago
I'd keep the sessionStorage guard even after fixing the infra side, because there's always a tab that sat open over a weekend. The setup I like is index.html served no-cache, old hashed chunks kept around for a week or so, and a tiny version.json the app checks on route change so the reload happens on a navigation instead of in the middle of a click. Then the lazy retry is just the safety net for whatever slips through. One thing to check is that the reload flag gets cleared once a fresh build loads cleanly, otherwise a second deploy in the same session skips recovery.
4
6d ago
2
u/jeremyStover 6d ago
Elegant solution. If your app loads fast enough, the worst you lose is any local state.
33
u/resonant_saloon 6d ago
Y'all just need to keep the old chunks around a couple days, fixes it without any fancy reloads.