← Back to the library
FrontendApplied · 7 min

Users see an old version or chunks fail to load after a deploy

“Right after a release, error tracking fills with ChunkLoadError or "Failed to fetch dynamically imported module", some users still see last week's UI, and a hard refresh fixes it for them.”

THE PRINCIPLE

Hashed assets are immutable and cached forever; the HTML that points at them is revalidated on every load. Old assets stay available for a while after each deploy, and the app has a planned path for when a chunk is missing anyway.

FIRST MOVES

  1. Check the failing URL: a 404 (or an HTML fallback served with a JS URL) for a hash from the previous build confirms version skew.
  2. Serve index.html (and any SPA fallback route) with Cache-Control: no-cache, and hashed assets with max-age=31536000, immutable; verify at the CDN, not just the origin.
  3. Keep the previous one or two builds' assets deployed (or use the host's skew protection) so open tabs can finish loading lazy chunks.
  4. Handle the failure: listen for vite:preloadError (or catch ChunkLoadError in webpack apps) and reload once, guarded against reload loops.
  5. If a service worker precaches the app, ship an update flow: detect the waiting worker, prompt the user, then skipWaiting and reload on controllerchange.

TOOLS: THEN → NOW

Asking users to hard refreshno-cache HTML, immutable hashed assets, and a one-shot reload on chunk errors
skipWaiting() unconditionally on installA prompted update (Workbox messageSkipWaiting) so the page and worker do not mix versions

PATTERN SNAPSHOT

// Vite: a lazy chunk from an older build is gone; reload once into the new build.
window.addEventListener("vite:preloadError", (event) => {
  const key = "reloaded-for-" + location.pathname;
  if (sessionStorage.getItem(key)) return; // let the error surface
  sessionStorage.setItem(key, "1");
  event.preventDefault();
  location.reload();
});

// Headers (CDN or host config):
//   /index.html   Cache-Control: no-cache
//   /assets/*     Cache-Control: max-age=31536000, immutable

CLOSE THE AI. EXPLAIN THIS.

A user left a tab open over a deploy and clicks into a lazy route. Walk through why it fails, and which two server-side settings plus one client-side handler prevent it.

HOW IT WORKS UNDERNEATH

SOURCES

Guide reviewed