Skip to content

Commit a00940b

Browse files
committed
chore: fix chunk error
1 parent f3c6281 commit a00940b

3 files changed

Lines changed: 141 additions & 6 deletions

File tree

app/layout.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import App from '@/components/layout/app';
66
import { getGitData } from '@/lib/get-git';
77
import { getUAFromServer } from '@/lib/get-os';
88
import Script from 'next/script';
9+
import { ChunkLoadErrorBoundary } from '@/components/error-boundary/chunk-load-error-boundary';
910

1011
export const viewport: Viewport = {
1112
width: 'device-width',
@@ -86,12 +87,14 @@ export default async function RootLayout({ children }: { children: React.ReactNo
8687
</Script>
8788
</head>
8889
<body id={'body'}>
89-
<App
90-
ua={ua}
91-
gitData={gitData}
92-
>
93-
{children}
94-
</App>
90+
<ChunkLoadErrorBoundary>
91+
<App
92+
ua={ua}
93+
gitData={gitData}
94+
>
95+
{children}
96+
</App>
97+
</ChunkLoadErrorBoundary>
9598
</body>
9699
</html>
97100
);
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
'use client';
2+
3+
import { Component, ReactNode } from 'react';
4+
5+
interface Props {
6+
children: ReactNode;
7+
}
8+
9+
interface State {
10+
hasError: boolean;
11+
error?: Error;
12+
}
13+
14+
/**
15+
* Error boundary to handle ChunkLoadError
16+
* Automatically reloads the page once when chunk loading fails
17+
* This typically happens after deployments when cached HTML
18+
* tries to load new chunk files that have different hashes
19+
*/
20+
export class ChunkLoadErrorBoundary extends Component<Props, State> {
21+
constructor(props: Props) {
22+
super(props);
23+
this.state = { hasError: false };
24+
}
25+
26+
componentDidMount() {
27+
if (typeof window === 'undefined') {
28+
return;
29+
}
30+
31+
// Only clear the reload marker when we reach a clean render
32+
if (!this.state.hasError && sessionStorage.getItem('chunk-load-error-reloaded')) {
33+
sessionStorage.removeItem('chunk-load-error-reloaded');
34+
}
35+
}
36+
37+
private handleManualReload = () => {
38+
if (typeof window === 'undefined') {
39+
return;
40+
}
41+
42+
sessionStorage.removeItem('chunk-load-error-reloaded');
43+
window.location.reload();
44+
};
45+
46+
static getDerivedStateFromError(error: Error): State {
47+
// Check if it's a chunk loading error
48+
const isChunkLoadError =
49+
error.name === 'ChunkLoadError' ||
50+
error.message.includes('Loading chunk') ||
51+
error.message.includes('ChunkLoadError');
52+
53+
return { hasError: isChunkLoadError, error };
54+
}
55+
56+
componentDidCatch(error: Error) {
57+
// Only handle chunk load errors
58+
const isChunkLoadError =
59+
error.name === 'ChunkLoadError' ||
60+
error.message.includes('Loading chunk') ||
61+
error.message.includes('ChunkLoadError');
62+
63+
if (isChunkLoadError) {
64+
console.warn('[ChunkLoadError] Detected chunk loading failure, reloading page...');
65+
66+
// Check if we've already reloaded (prevent infinite loops)
67+
const hasReloaded = sessionStorage.getItem('chunk-load-error-reloaded');
68+
69+
if (!hasReloaded) {
70+
// Mark that we're reloading
71+
sessionStorage.setItem('chunk-load-error-reloaded', 'true');
72+
73+
// Reload the page to get fresh HTML and chunks
74+
window.location.reload();
75+
} else {
76+
// Already reloaded once, don't loop
77+
console.error('[ChunkLoadError] Already reloaded once, not reloading again');
78+
sessionStorage.removeItem('chunk-load-error-reloaded');
79+
}
80+
}
81+
}
82+
83+
render() {
84+
if (this.state.hasError) {
85+
// Show a brief loading message while reloading
86+
return (
87+
<div
88+
style={{
89+
display: 'flex',
90+
alignItems: 'center',
91+
justifyContent: 'center',
92+
minHeight: '100vh',
93+
flexDirection: 'column',
94+
gap: '16px',
95+
padding: '20px',
96+
textAlign: 'center',
97+
}}
98+
>
99+
<div style={{ fontSize: '18px', fontWeight: 500 }}>Updating...</div>
100+
<div style={{ fontSize: '14px', color: '#666' }}>
101+
Loading the latest version
102+
</div>
103+
<button
104+
onClick={this.handleManualReload}
105+
style={{
106+
padding: '10px 16px',
107+
borderRadius: '8px',
108+
border: '1px solid #ccc',
109+
background: '#fff',
110+
fontSize: '14px',
111+
cursor: 'pointer',
112+
}}
113+
>
114+
Reload now
115+
</button>
116+
</div>
117+
);
118+
}
119+
120+
return this.props.children;
121+
}
122+
}

next.config.mjs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,16 @@ const nextConfig = {
121121
source: '/(.*)',
122122
headers: securityHeaders,
123123
},
124+
{
125+
// Cache Next.js static chunks with hashed filenames (immutable)
126+
source: '/_next/static/:path*',
127+
headers: [
128+
{
129+
key: 'Cache-Control',
130+
value: 'public, max-age=31536000, immutable',
131+
},
132+
],
133+
},
124134
{
125135
// Cache SVG sprite sheet for 1 year (immutable)
126136
source: '/images/company-logos-sprite.svg',

0 commit comments

Comments
 (0)