Files
HealthLog/src/app/error.tsx
T
Marc-André Bombeck 82241063f6 fix(app-error): auto-recover from stale-shell ChunkLoadError
After a deploy the cached SPA shell still references the old chunk
filenames. Next.js fetches them, the new server 404s the request,
and the user sees the generic error surface — typically with no
context other than "Failed to load chunk". The fix on the user's
side is a hard reload; before this commit they had to discover it
themselves.

`AppError` now detects the chunk-load family of errors
(`ChunkLoadError`, `Loading chunk`, `Failed to load chunk`,
`Failed to fetch dynamically imported module`) and triggers a single
`window.location.reload()`. `sessionStorage` gates the auto-reload
to once per session so an unrecoverable error doesn't loop into a
reload storm. Browsers in strict-privacy mode fall through to the
error UI cleanly.
2026-05-17 23:56:04 +02:00

57 lines
1.9 KiB
TypeScript

"use client";
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,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
// Surface to any client-side error tracker that's already hooked up.
if (typeof window !== "undefined") {
const g = window as typeof window & {
__healthlog_onError?: (err: Error & { digest?: string }) => void;
};
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 <ErrorDetails error={error} reset={reset} />;
}