import { lazy, type ComponentType, type LazyExoticComponent } from 'react'; /** * `lazy()` with recovery from chunk-load failures. * * A lazy route's `import()` can fail transiently: * • after a deploy, an already-open page still references chunk URLs that no * longer exist on the server; * • in dev, Vite's dependency optimizer can answer 504 ("Outdated Optimize * Dep") for an optimized dependency after it re-optimized. * * Either way the import rejects and, with a plain `lazy()`, the route error * boundary shows a dead "This page slammed" screen the user can do nothing with. * * Instead of failing hard we retry once, and if it still fails we do a single * full reload per session (which fetches a fresh index + module graph). The * session flag prevents a reload loop if the failure is genuinely persistent — * then the error boundary is the correct last resort. */ const RELOAD_FLAG = 'skatespots:chunk-reload'; /** * Pages take no props, so any props-compatible component type works here. Using * `ComponentType` (rather than `any`) keeps this type-safe while still * accepting every page component in the router. */ type AnyComponent = ComponentType; function isChunkLoadError(error: unknown): boolean { const message = error instanceof Error ? error.message : String(error); return /Failed to fetch dynamically imported module|Importing a module script failed|error loading dynamically imported module|Outdated Optimize Dep|Failed to load module script/i.test( message, ); } function hasReloadedThisSession(): boolean { try { return sessionStorage.getItem(RELOAD_FLAG) === '1'; } catch { return false; } } function markReloaded(): void { try { sessionStorage.setItem(RELOAD_FLAG, '1'); } catch { /* sessionStorage unavailable — the retry below still runs */ } } function clearReloadFlag(): void { try { sessionStorage.removeItem(RELOAD_FLAG); } catch { /* ignore */ } } export function lazyWithRetry( factory: () => Promise<{ default: AnyComponent }>, ): LazyExoticComponent { return lazy(async () => { try { const module = await factory(); // A clean load means the module graph is healthy again; allow a future // (unrelated) failure to trigger its own reload. clearReloadFlag(); return module; } catch (error) { if (!isChunkLoadError(error)) throw error; if (!hasReloadedThisSession()) { markReloaded(); // Kick a full reload and suspend this import until the new document // takes over. Never resolves — the navigation unloads the page. window.location.reload(); return await new Promise(() => undefined); } // Already reloaded once this session. The optimizer or CDN may have just // needed a moment, so retry the import before showing the error boundary. await new Promise((resolve) => setTimeout(resolve, 1000)); const module = await factory(); clearReloadFlag(); return module; } }); }