← All posts

React.lazy and the blank screen after deploy

Stale HTML in the tab, new bundle on the CDN — chunk missing. A global listener and cache-bust in ~15 lines.

React.lazy and the blank screen after deploy
Contents

In brief

After an SPA deploy, a user with an open tab can hit a blank screen: React.lazy() requests a chunk with an old hash while the CDN already serves a new bundle. A Dev.to post shares a small global handler that reloads the page with a cache-busting query param.

What happened

Classic flow: the browser keeps an old index.html in memory; navigating to a route triggers a dynamic import that 404s or fails on the network. The console shows variants like Failed to fetch dynamically imported module or Loading chunk N failed — wording differs across Chrome, Safari, and older Webpack builds.

Most users will not hard-reload — they leave.

Why it matters

Code splitting saves first paint but ties shell version to chunk version. Any deploy without an HTML cache strategy leaves sessions stranded between releases.

Symptom Cause Without a fix
Blank screen after navigation Chunk removed/renamed Lost conversions
Only some users affected Stale tabs Hard to reproduce in support
Hard reload helps Fresh index.html with new hashes Does not scale

Fifteen lines of handler code are cheaper than losing sessions after every release.

In practice

  1. Attach window.addEventListener('error', …) with regexes for chunk, dynamic import, CSS chunk, and Safari’s «Importing a module script failed».
  2. On match — location.replace() with ?_r=<timestamp> to fetch fresh HTML without an extra history entry (back button still works).
  3. Set a «already reloaded» flag to avoid loops on a real network outage.
  4. Mirror logic on unhandledrejection if the lazy import fails inside a promise.
  5. Test: deploy a new build, keep an old tab, navigate to a lazy route — the page should recover on its own.

Long term, pair this with cache policy: short TTL or no-cache for index.html, long cache for hashed assets.

Takeaway

React.lazy plus frequent deploys without recovery is a predictable incident. A global listener does not replace correct HTML caching, but it protects live tabs during rollout.