{"record":{"id":"e5b3263d4c6c8215","repo":"remix-run/react-router","slug":"detected-manifest-version-mismatch-reloading","errorCode":null,"errorMessage":"Detected manifest version mismatch, reloading...","messagePattern":"Detected manifest version mismatch, reloading\\.\\.\\.","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/react-router/lib/dom/ssr/fog-of-war.ts","lineNumber":285,"sourceCode":"  try {\n    // This will hard reload the destination path on navigations, or the\n    // current path on fetcher calls\n    if (sessionStorage.getItem(MANIFEST_VERSION_STORAGE_KEY) === version) {\n      // We've already tried fixing for this version, don't try again to\n      // avoid loops - just let this navigation/fetch 404\n      console.error(\n        \"Unable to discover routes due to manifest version mismatch.\",\n      );\n      return true;\n    }\n\n    sessionStorage.setItem(MANIFEST_VERSION_STORAGE_KEY, version);\n  } catch {\n    // Session storage unavailable\n  }\n\n  window.location.href = errorReloadPath;\n  console.warn(\"Detected manifest version mismatch, reloading...\");\n\n  // Stall here and let the browser reload and avoid triggering a flash of\n  // an ErrorBoundary if we threw (same thing we do in `loadRouteModule()`)\n  await new Promise(() => {\n    // check out of this hook cause the DJs never gonna re[s]olve this\n  });\n  return true;\n}\n\nexport async function fetchAndApplyManifestPatches(\n  paths: string[],\n  errorReloadPath: string | null,\n  manifest: AssetsManifest,\n  routeModules: RouteModules,\n  ssr: boolean,\n  isSpaMode: boolean,\n  basename: string | undefined,\n  manifestPath: string,","sourceCodeStart":267,"sourceCodeEnd":303,"githubUrl":"https://github.com/remix-run/react-router/blob/7aea711dd1ae2bc5a076d13ff17291829690fa74/packages/react-router/lib/dom/ssr/fog-of-war.ts#L267-L303","documentation":"With lazy route discovery (fog of war), the browser fetches route manifest patches from /__manifest. If a patch response carries a version that differs from the manifest baked into the running page (typical after a new deploy while a tab stayed open), handleClientVersionMismatch stores the new version in sessionStorage (a loop guard — one reload attempt per version) and hard-navigates window.location to errorReloadPath. The never-resolving promise then stalls the initiator so the browser reloads instead of flashing an ErrorBoundary. This is an automatic recovery mechanism, not a crash.","triggerScenarios":"A navigation or fetcher call to a not-yet-discovered route returns a version-mismatched manifest patch while an errorReloadPath is available — i.e., the server is serving a newer build than the one the current document was rendered from.","commonSituations":"Users with long-lived tabs across a deploy; CDN or edge caching of /__manifest (or the HTML) serving stale versions; rolling deploys where different instances serve different builds; multiple app versions behind one domain.","solutions":["Treat it as self-healing — no app code change is required; the reload syncs the tab to the new build","Serve the manifest endpoint (and index HTML) with `Cache-Control: no-store` so patch fetches always hit the current origin build","Deploy atomically (build to a new directory, swap a symlink) so HTML, assets, and manifest flip versions together","If it reload-loops, the loop guard logs 'Unable to discover routes due to manifest version mismatch.' — verify all instances serve the same build and purge the CDN cache for /__manifest and HTML"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"// framework-internal recovery: one guarded hard reload per manifest version\n// (sessionStorage key 'react-router-manifest-version' prevents loops).\n// App-level equivalent if you surface your own version banner:\ntry {\n  let res = await fetch(\"/__manifest?version=\" + window.__remixManifest.version);\n  if (!res.ok) location.reload();\n} catch { /* offline: retry on next focus */ }","preventionTips":["Serve /__manifest and index HTML with Cache-Control: no-store so stale versions are never handed out","Cut over deploys atomically (build dir + symlink swap) instead of overwriting files in place","Confirm every instance behind the load balancer serves the same build before finishing a rolling deploy"],"tags":["fog-of-war","manifest","deploy","stale-client","lazy-routes"],"backgroundTag":"stale-client-after-deploy","analyzedSha":"7aea711dd1ae2bc5a076d13ff17291829690fa74","analyzedAt":"2026-08-18T18:04:14.938Z","contentChangedAt":"2026-08-18T18:04:14.938Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}