diff --git a/src/app/error.tsx b/src/app/error.tsx index b1a20f6ba..0077dc484 100644 --- a/src/app/error.tsx +++ b/src/app/error.tsx @@ -3,6 +3,19 @@ import { useEffect } from "react"; import { ErrorDetails } from "@/components/error-details"; +const CHUNK_RELOAD_KEY = "healthlog:chunk-reload-attempted"; + +function isChunkLoadError(err: Error & { digest?: string }): boolean { + if (err.name === "ChunkLoadError") return true; + const msg = err.message ?? ""; + return ( + msg.includes("Loading chunk") || + msg.includes("Loading CSS chunk") || + msg.includes("Failed to load chunk") || + msg.includes("Failed to fetch dynamically imported module") + ); +} + export default function AppError({ error, reset, @@ -18,6 +31,25 @@ export default function AppError({ }; g.__healthlog_onError?.(error); } + + // v1.4.38.3 — auto-recover from a stale-shell chunk-load error. + // After a deploy the cached shell still references old chunk + // filenames; they 404 and Next.js surfaces them as `ChunkLoadError`. + // A single hard reload fetches the new shell + chunk graph. + // sessionStorage gates the auto-reload so an error we can't recover + // from (e.g. the new build is also missing the chunk) doesn't loop. + if (typeof window !== "undefined" && isChunkLoadError(error)) { + try { + const already = window.sessionStorage.getItem(CHUNK_RELOAD_KEY); + if (!already) { + window.sessionStorage.setItem(CHUNK_RELOAD_KEY, String(Date.now())); + window.location.reload(); + } + } catch { + // sessionStorage can throw under strict privacy modes — fall + // through to the error UI in that case. + } + } }, [error]); return ;