Skip to content

Commit a168fc2

Browse files
kuco23claude
andcommitted
claude:fix: auto-reload on stale lazy-route chunk to recover stuck navigations
After a redeploy, tabs holding old HTML reference chunk filenames that no longer exist; the dynamic import rejects inside startTransition, React keeps the old UI mounted and the URL change has no visible effect. Wrap each route's import() with a one-shot sessionStorage-gated reload and add a ChunkErrorBoundary as a backstop with a manual reload button. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 07af86e commit a168fc2

3 files changed

Lines changed: 90 additions & 16 deletions

File tree

src/assets/css/custom.css

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,3 +284,25 @@
284284
scroll-padding-left: 0;
285285
}
286286
}
287+
288+
/* LAZY LOAD ERROR FALLBACK */
289+
290+
.lazy-load-error {
291+
max-width: 480px;
292+
margin: 120px auto;
293+
padding: 32px;
294+
text-align: center;
295+
border: 1px solid rgba(255, 255, 255, 0.08);
296+
border-radius: 12px;
297+
background: rgba(255, 255, 255, 0.015);
298+
}
299+
300+
.lazy-load-error h3 {
301+
font-size: 1.2rem;
302+
margin-bottom: 10px;
303+
}
304+
305+
.lazy-load-error p {
306+
color: rgba(255, 255, 255, 0.6);
307+
margin-bottom: 22px;
308+
}

src/route/lazy.tsx

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,63 @@
1-
import { Suspense } from "react";
1+
import { Component, lazy, Suspense, type ComponentType, type ReactNode } from "react";
22
import { PreloaderContent } from "~/components/ui/preloader";
33

4-
const Lazy = ({ children }) => (
5-
<Suspense fallback={<div className="preloader"><PreloaderContent /></div>}>
6-
{children}
7-
</Suspense>
8-
);
4+
const RELOAD_FLAG = 'stakecore:chunk-reload-attempted'
95

10-
export default Lazy;
6+
export function lazyWithReload<T extends ComponentType<any>>(
7+
factory: () => Promise<{ default: T }>
8+
) {
9+
return lazy(async () => {
10+
try {
11+
const mod = await factory()
12+
sessionStorage.removeItem(RELOAD_FLAG)
13+
return mod
14+
} catch (err) {
15+
if (sessionStorage.getItem(RELOAD_FLAG) !== '1') {
16+
sessionStorage.setItem(RELOAD_FLAG, '1')
17+
window.location.reload()
18+
return new Promise<{ default: T }>(() => {})
19+
}
20+
throw err
21+
}
22+
})
23+
}
24+
25+
class ChunkErrorBoundary extends Component<{ children: ReactNode }, { hasError: boolean }> {
26+
state = { hasError: false }
27+
28+
static getDerivedStateFromError() {
29+
return { hasError: true }
30+
}
31+
32+
componentDidCatch(error: Error) {
33+
console.error('Lazy route load failed:', error)
34+
}
35+
36+
handleReload = () => {
37+
sessionStorage.removeItem(RELOAD_FLAG)
38+
window.location.reload()
39+
}
40+
41+
render() {
42+
if (this.state.hasError) {
43+
return (
44+
<div className="lazy-load-error">
45+
<h3>Couldn't load this page</h3>
46+
<p>A new version may have been deployed. Reloading should fix it.</p>
47+
<button className="theme-btn" onClick={this.handleReload}>Reload</button>
48+
</div>
49+
)
50+
}
51+
return this.props.children
52+
}
53+
}
54+
55+
const Lazy = ({ children }: { children: ReactNode }) => (
56+
<ChunkErrorBoundary>
57+
<Suspense fallback={<div className="preloader"><PreloaderContent /></div>}>
58+
{children}
59+
</Suspense>
60+
</ChunkErrorBoundary>
61+
)
62+
63+
export default Lazy

src/route/router.jsx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
1-
import { lazy } from "react";
21
import { createHashRouter } from "react-router-dom";
32
import RootLayout from "../layout/root";
43
import Home from "../pages/home";
5-
import Lazy from "./lazy";
4+
import Lazy, { lazyWithReload } from "./lazy";
65

7-
const Contact = lazy(() => import("../pages/contact"));
8-
const About = lazy(() => import("../pages/about"));
9-
const Protocols = lazy(() => import("../pages/protocols"));
10-
const AvalancheValidatorProject = lazy(() => import("../pages/protocols/avalanche-validator/page"));
11-
const FlareValidatorProject = lazy(() => import("../pages/protocols/flare-validator/page"));
12-
const FlareFspProject = lazy(() => import("../pages/protocols/flare-fsp/page"));
13-
const SongbirdFspProject = lazy(() => import("../pages/protocols/songbird-fsp/page"));
6+
const Contact = lazyWithReload(() => import("../pages/contact"));
7+
const About = lazyWithReload(() => import("../pages/about"));
8+
const Protocols = lazyWithReload(() => import("../pages/protocols"));
9+
const AvalancheValidatorProject = lazyWithReload(() => import("../pages/protocols/avalanche-validator/page"));
10+
const FlareValidatorProject = lazyWithReload(() => import("../pages/protocols/flare-validator/page"));
11+
const FlareFspProject = lazyWithReload(() => import("../pages/protocols/flare-fsp/page"));
12+
const SongbirdFspProject = lazyWithReload(() => import("../pages/protocols/songbird-fsp/page"));
1413

1514

1615
export const router = createHashRouter([

0 commit comments

Comments
 (0)