update bug fixing
This commit is contained in:
parent
d10bd92960
commit
b7679cbcb1
|
|
@ -19,6 +19,7 @@ import { NextIntlClientProvider } from "next-intl";
|
||||||
import { getMessages } from "next-intl/server";
|
import { getMessages } from "next-intl/server";
|
||||||
import DirectionProvider from "@/providers/direction-provider";
|
import DirectionProvider from "@/providers/direction-provider";
|
||||||
import AuthProvider from "@/providers/auth.provider";
|
import AuthProvider from "@/providers/auth.provider";
|
||||||
|
import ChunkLoadErrorHandler from "@/components/chunk-load-error-handler";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Media Hub | POLRI",
|
title: "Media Hub | POLRI",
|
||||||
|
|
@ -96,6 +97,7 @@ export default async function RootLayout({
|
||||||
/>
|
/>
|
||||||
</head>
|
</head>
|
||||||
<body className={`${inter.className} dashcode-app`}>
|
<body className={`${inter.className} dashcode-app`}>
|
||||||
|
<ChunkLoadErrorHandler />
|
||||||
<NextIntlClientProvider messages={messages} locale={locale}>
|
<NextIntlClientProvider messages={messages} locale={locale}>
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<ThemeProvider attribute="class" defaultTheme="light">
|
<ThemeProvider attribute="class" defaultTheme="light">
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component to handle ChunkLoadError automatically
|
||||||
|
* When a chunk fails to load (usually after deployment),
|
||||||
|
* this component will force refresh the page once
|
||||||
|
*/
|
||||||
|
export default function ChunkLoadErrorHandler() {
|
||||||
|
useEffect(() => {
|
||||||
|
// Key to track if we've already refreshed for this error
|
||||||
|
const REFRESH_KEY = "chunk-load-error-refreshed";
|
||||||
|
|
||||||
|
// Handle unhandled promise rejections (for dynamic imports)
|
||||||
|
const handlePromiseRejection = (event: PromiseRejectionEvent) => {
|
||||||
|
const error = event.reason;
|
||||||
|
|
||||||
|
// Check if it's a ChunkLoadError
|
||||||
|
if (
|
||||||
|
error?.name === "ChunkLoadError" ||
|
||||||
|
error?.message?.includes("Loading chunk") ||
|
||||||
|
error?.message?.includes("ChunkLoadError") ||
|
||||||
|
error?.message?.includes("Failed to fetch dynamically imported module") ||
|
||||||
|
error?.message?.includes("failed to load resource")
|
||||||
|
) {
|
||||||
|
handleChunkLoadError();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle regular errors
|
||||||
|
const handleError = (event: ErrorEvent) => {
|
||||||
|
const error = event.error;
|
||||||
|
|
||||||
|
// Check if it's a ChunkLoadError
|
||||||
|
if (
|
||||||
|
error?.name === "ChunkLoadError" ||
|
||||||
|
event.message?.includes("Loading chunk") ||
|
||||||
|
event.message?.includes("ChunkLoadError") ||
|
||||||
|
event.message?.includes("Failed to fetch dynamically imported module")
|
||||||
|
) {
|
||||||
|
handleChunkLoadError();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChunkLoadError = () => {
|
||||||
|
// Check if we've already refreshed
|
||||||
|
const hasRefreshed = sessionStorage.getItem(REFRESH_KEY);
|
||||||
|
|
||||||
|
if (!hasRefreshed) {
|
||||||
|
console.log("ChunkLoadError detected. Refreshing page...");
|
||||||
|
|
||||||
|
// Mark that we've refreshed
|
||||||
|
sessionStorage.setItem(REFRESH_KEY, "true");
|
||||||
|
|
||||||
|
// Force reload the page (bypassing cache)
|
||||||
|
window.location.reload();
|
||||||
|
} else {
|
||||||
|
console.error("ChunkLoadError persists after refresh. There might be a deeper issue.");
|
||||||
|
|
||||||
|
// Clear the flag after 30 seconds in case user navigates away and comes back
|
||||||
|
setTimeout(() => {
|
||||||
|
sessionStorage.removeItem(REFRESH_KEY);
|
||||||
|
}, 30000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add event listeners
|
||||||
|
window.addEventListener("error", handleError);
|
||||||
|
window.addEventListener("unhandledrejection", handlePromiseRejection);
|
||||||
|
|
||||||
|
// Cleanup event listeners
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("error", handleError);
|
||||||
|
window.removeEventListener("unhandledrejection", handlePromiseRejection);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// This component doesn't render anything
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
# Chunk Load Error Handler
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
ChunkLoadError terjadi ketika aplikasi Next.js mencoba memuat chunk JavaScript yang tidak lagi tersedia, biasanya terjadi karena:
|
||||||
|
- Deployment baru sementara user masih membuka halaman lama
|
||||||
|
- File chunk sudah berubah atau dihapus
|
||||||
|
- Network issues
|
||||||
|
- Cache issues
|
||||||
|
|
||||||
|
Error ini menyebabkan tampilan web bermasalah dan user tidak bisa menggunakan fitur tertentu.
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
Implementasi automatic force refresh yang akan:
|
||||||
|
1. Mendeteksi ChunkLoadError secara otomatis
|
||||||
|
2. Melakukan force refresh **hanya sekali** untuk mencegah infinite reload loop
|
||||||
|
3. Menggunakan `sessionStorage` untuk tracking refresh state
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
### 1. Component: `chunk-load-error-handler.tsx`
|
||||||
|
Component client-side yang mendengarkan error events:
|
||||||
|
- `window.error` - untuk menangkap error reguler
|
||||||
|
- `window.unhandledrejection` - untuk menangkap promise rejections dari dynamic imports
|
||||||
|
|
||||||
|
Component ini akan:
|
||||||
|
- Mendeteksi berbagai variasi ChunkLoadError
|
||||||
|
- Check apakah sudah pernah refresh (via sessionStorage)
|
||||||
|
- Jika belum, lakukan `window.location.reload()` untuk force refresh
|
||||||
|
- Jika sudah refresh tapi masih error, tidak akan refresh lagi (mencegah infinite loop)
|
||||||
|
|
||||||
|
### 2. Integration di Root Layout
|
||||||
|
Component ditambahkan di root layout (`app/[locale]/layout.tsx`) agar berfungsi di seluruh aplikasi.
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
```
|
||||||
|
User opens app → Deployment happens → User navigates → ChunkLoadError occurs
|
||||||
|
↓
|
||||||
|
Handler detects error
|
||||||
|
↓
|
||||||
|
Check sessionStorage
|
||||||
|
↓
|
||||||
|
No refresh yet? → Yes → Set flag & Reload
|
||||||
|
↓
|
||||||
|
Already refreshed? → Show error in console
|
||||||
|
```
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
✅ User experience lebih baik - no more broken page
|
||||||
|
✅ Automatic recovery dari ChunkLoadError
|
||||||
|
✅ Aman - hanya refresh sekali, tidak ada infinite loop
|
||||||
|
✅ Bekerja di background tanpa mengganggu user
|
||||||
|
✅ Works untuk semua tipe lazy-loaded components
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
Untuk test handler ini:
|
||||||
|
1. Build aplikasi: `npm run build`
|
||||||
|
2. Start production: `npm run start`
|
||||||
|
3. Buka aplikasi di browser
|
||||||
|
4. Build ulang aplikasi (tanpa menutup browser)
|
||||||
|
5. Navigate ke halaman yang menggunakan dynamic imports
|
||||||
|
6. Handler akan otomatis refresh jika terjadi ChunkLoadError
|
||||||
|
|
||||||
|
## Monitoring
|
||||||
|
Check browser console untuk melihat log:
|
||||||
|
- `"ChunkLoadError detected. Refreshing page..."` - saat refresh pertama kali
|
||||||
|
- `"ChunkLoadError persists after refresh..."` - jika masih error setelah refresh
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- SessionStorage akan di-clear otomatis setelah 30 detik jika error persists
|
||||||
|
- Handler tidak render apapun (return null)
|
||||||
|
- Zero impact pada performance
|
||||||
|
- Compatible dengan semua browser modern
|
||||||
|
|
||||||
Loading…
Reference in New Issue