fix: Add ChunkLoadError handling with automatic page reload

- Detect ChunkLoadError in global error handler
- Automatically clear Next.js cache and reload page when chunk errors occur
- Add proper cache headers for Next.js static chunks (immutable caching)
- Handle ChunkLoadError in both error events and unhandled promise rejections
- Prevents users from seeing chunk load errors after deployments

Fixes production issue where admin page fails to load after new deployment due to stale chunks in browser cache
This commit is contained in:
2026-01-22 15:31:38 +02:00
parent 2e1f2f0365
commit 700bf58ca6
2 changed files with 70 additions and 0 deletions

View File

@@ -61,6 +61,26 @@ const nextConfig: NextConfig = {
},
],
},
{
// Next.js chunks - ensure proper caching with versioning
source: "/_next/static/chunks/:path*",
headers: [
{
key: "Cache-Control",
value: "public, max-age=31536000, immutable",
},
],
},
{
// Next.js static files - ensure proper caching
source: "/_next/static/:path*",
headers: [
{
key: "Cache-Control",
value: "public, max-age=31536000, immutable",
},
],
},
// Security headers are handled in src/proxy.ts middleware (single source of truth)
// This ensures consistent headers across all routes and avoids conflicts
];

View File

@@ -123,11 +123,55 @@ export default function RootLayout({
const originalWarn = console.warn;
const originalLog = console.log;
// Handle ChunkLoadError - automatically reload page
let chunkErrorReloadAttempted = false;
const handleChunkLoadError = function(error) {
const message = error?.message || error?.toString() || '';
const lowerMessage = message.toLowerCase();
// Check if this is a ChunkLoadError
if (
(lowerMessage.includes('chunkloaderror') ||
lowerMessage.includes('failed to load chunk') ||
lowerMessage.includes('loading chunk') ||
error?.name === 'ChunkLoadError') &&
!chunkErrorReloadAttempted
) {
chunkErrorReloadAttempted = true;
console.warn('ChunkLoadError detected - reloading page to fetch latest chunks...');
// Clear Next.js cache and reload
if ('caches' in window) {
caches.keys().then(function(names) {
for (let name of names) {
if (name.includes('nextjs') || name.includes('_next')) {
caches.delete(name);
}
}
});
}
// Reload after a short delay to allow cache clearing
setTimeout(function() {
window.location.reload();
}, 100);
return true;
}
return false;
};
// Intercept uncaught errors
window.addEventListener('error', function(event) {
const message = event.message || '';
const lowerMessage = message.toLowerCase();
// Handle ChunkLoadError first
if (handleChunkLoadError(event.error || event)) {
event.preventDefault();
return false;
}
// Suppress OuterLayoutRouter key warnings
if (
(lowerMessage.includes('key') || lowerMessage.includes('unique')) &&
@@ -153,6 +197,12 @@ export default function RootLayout({
const message = reason?.message || reason?.toString() || '';
const lowerMessage = message.toLowerCase();
// Handle ChunkLoadError in promise rejections
if (handleChunkLoadError(reason)) {
event.preventDefault();
return false;
}
// Suppress Supabase NetworkErrors
if (
lowerMessage.includes('networkerror') &&