From af9000498a11139215ae99ac065cea9b73a62b5c Mon Sep 17 00:00:00 2001 From: hanif salafi Date: Thu, 3 Jul 2025 21:35:45 +0700 Subject: [PATCH] feat: update bug fixing chunk --- app/layout.tsx | 6 +- .../landing-page/latest-and-popular.tsx | 12 +- components/layout/chunk-error-boundary.tsx | 104 ++++++++++++++++++ .../main/dashboard/chart/column-chart.tsx | 7 +- next.config.ts | 47 ++++++++ utils/chunk-error-handler.ts | 87 +++++++++++++++ utils/dynamic-import.ts | 70 ++++++++++++ 7 files changed, 325 insertions(+), 8 deletions(-) create mode 100644 components/layout/chunk-error-boundary.tsx create mode 100644 utils/chunk-error-handler.ts create mode 100644 utils/dynamic-import.ts diff --git a/app/layout.tsx b/app/layout.tsx index 63e2178..e9bc5ad 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,6 +1,8 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; +import ChunkErrorBoundary from "@/components/layout/chunk-error-boundary"; +import "@/utils/chunk-error-handler"; const geistSans = Geist({ variable: "--font-geist-sans", @@ -25,7 +27,9 @@ export default function RootLayout({ return ( - {children} + + {children} + ); diff --git a/components/landing-page/latest-and-popular.tsx b/components/landing-page/latest-and-popular.tsx index c3942b2..67bdd64 100644 --- a/components/landing-page/latest-and-popular.tsx +++ b/components/landing-page/latest-and-popular.tsx @@ -250,7 +250,11 @@ export default function LatestandPopular() {
{"article.title"}
{"article?.title"} { + constructor(props: Props) { + super(props); + this.state = { hasError: false }; + } + + static getDerivedStateFromError(error: Error): State { + // Check if it's a chunk loading error + if (error.name === 'ChunkLoadError' || error.message.includes('Loading chunk')) { + return { hasError: true, error }; + } + return { hasError: false }; + } + + componentDidCatch(error: Error, errorInfo: ErrorInfo) { + console.error('Chunk loading error:', error, errorInfo); + + // If it's a chunk loading error, try to reload the page + if (error.name === 'ChunkLoadError' || error.message.includes('Loading chunk')) { + this.setState({ hasError: true, error }); + } + } + + handleRetry = () => { + // Clear the error state and reload the page + this.setState({ hasError: false, error: undefined }); + window.location.reload(); + }; + + render() { + if (this.state.hasError) { + if (this.props.fallback) { + return this.props.fallback; + } + + return ( +
+
+
+
+ +
+

+ Chunk Loading Error +

+

+ There was an issue loading a part of the application. This usually happens when the application has been updated. +

+
+ +
+ + + +
+ + {process.env.NODE_ENV === 'development' && this.state.error && ( +
+ + Error Details (Development) + +
+                  {this.state.error.message}
+                
+
+ )} +
+
+ ); + } + + return this.props.children; + } +} + +export default ChunkErrorBoundary; \ No newline at end of file diff --git a/components/main/dashboard/chart/column-chart.tsx b/components/main/dashboard/chart/column-chart.tsx index 747bd02..d29575c 100644 --- a/components/main/dashboard/chart/column-chart.tsx +++ b/components/main/dashboard/chart/column-chart.tsx @@ -1,7 +1,7 @@ "use client"; import { getStatisticMonthly } from "@/service/article"; import React, { useEffect, useState } from "react"; -import dynamic from "next/dynamic"; +import { SafeReactApexChart } from "@/utils/dynamic-import"; type WeekData = { week: number; @@ -53,9 +53,6 @@ const ApexChartColumn = (props: { const [seriesComment, setSeriesComment] = useState([]); const [seriesView, setSeriesView] = useState([]); const [seriesShare, setSeriesShare] = useState([]); - const ReactApexChart = dynamic(() => import("react-apexcharts"), { - ssr: false, - }); useEffect(() => { initFetch(); @@ -132,7 +129,7 @@ const ApexChartColumn = (props: { return (
- { + // Handle chunk loading errors + config.optimization = { + ...config.optimization, + splitChunks: { + ...config.optimization.splitChunks, + chunks: 'all', + cacheGroups: { + ...config.optimization.splitChunks?.cacheGroups, + vendor: { + test: /[\\/]node_modules[\\/]/, + name: 'vendors', + chunks: 'all', + }, + // Separate CKEditor chunks + ckeditor: { + test: /[\\/]node_modules[\\/]@ckeditor[\\/]/, + name: 'ckeditor', + chunks: 'all', + priority: 20, + }, + // Separate ApexCharts chunks + apexcharts: { + test: /[\\/]node_modules[\\/](apexcharts|react-apexcharts)[\\/]/, + name: 'apexcharts', + chunks: 'all', + priority: 20, + }, + }, + }, + }; + + // Add error handling for chunk loading + if (!isServer) { + config.output = { + ...config.output, + chunkFilename: '[name].[chunkhash].js', + filename: '[name].[chunkhash].js', + }; + } + + return config; + }, + // Add experimental features for better chunk handling + experimental: { + optimizePackageImports: ['@ckeditor/ckeditor5-react', 'react-apexcharts'], + }, }; export default nextConfig; diff --git a/utils/chunk-error-handler.ts b/utils/chunk-error-handler.ts new file mode 100644 index 0000000..fc09c39 --- /dev/null +++ b/utils/chunk-error-handler.ts @@ -0,0 +1,87 @@ +// Global chunk loading error handler +export function setupChunkErrorHandler() { + if (typeof window === 'undefined') return; + + // Handle chunk loading errors + window.addEventListener('error', (event) => { + const error = event.error; + + // Check if it's a chunk loading error + if ( + error?.name === 'ChunkLoadError' || + error?.message?.includes('Loading chunk') || + error?.message?.includes('Failed to fetch') + ) { + console.warn('Chunk loading error detected:', error); + + // Prevent the error from being logged to console + event.preventDefault(); + + // Show a user-friendly message + const message = document.createElement('div'); + message.style.cssText = ` + position: fixed; + top: 20px; + right: 20px; + background: #fef2f2; + border: 1px solid #fecaca; + color: #dc2626; + padding: 12px 16px; + border-radius: 8px; + font-size: 14px; + z-index: 9999; + max-width: 300px; + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); + `; + message.innerHTML = ` +
+ ⚠️ + Application update detected. Please refresh the page. +
+ + `; + + document.body.appendChild(message); + + // Auto-remove after 10 seconds + setTimeout(() => { + if (message.parentNode) { + message.parentNode.removeChild(message); + } + }, 10000); + } + }); + + // Handle unhandled promise rejections (which might include chunk loading errors) + window.addEventListener('unhandledrejection', (event) => { + const error = event.reason; + + if ( + error?.name === 'ChunkLoadError' || + error?.message?.includes('Loading chunk') || + error?.message?.includes('Failed to fetch') + ) { + console.warn('Unhandled chunk loading rejection:', error); + event.preventDefault(); + + // Reload the page after a short delay + setTimeout(() => { + window.location.reload(); + }, 1000); + } + }); +} + +// Auto-setup when this module is imported +if (typeof window !== 'undefined') { + setupChunkErrorHandler(); +} \ No newline at end of file diff --git a/utils/dynamic-import.ts b/utils/dynamic-import.ts new file mode 100644 index 0000000..2469892 --- /dev/null +++ b/utils/dynamic-import.ts @@ -0,0 +1,70 @@ +import dynamic from 'next/dynamic'; +import { ComponentType } from 'react'; + +interface DynamicImportOptions { + ssr?: boolean; + loading?: () => React.ReactElement; + retries?: number; + retryDelay?: number; +} + +export function createSafeDynamicImport>( + importFn: () => Promise<{ default: T }>, + options: DynamicImportOptions = {} +) { + const { ssr = false, loading, retries = 3, retryDelay = 1000 } = options; + + return dynamic( + () => { + return new Promise((resolve, reject) => { + let attempts = 0; + + const attemptImport = async () => { + try { + const module = await importFn(); + resolve(module.default); + } catch (error) { + attempts++; + + // Check if it's a chunk loading error + if ( + (error as any)?.name === 'ChunkLoadError' || + (error as any)?.message?.includes('Loading chunk') || + (error as any)?.message?.includes('Failed to fetch') + ) { + if (attempts < retries) { + console.warn(`Chunk loading failed, retrying... (${attempts}/${retries})`); + setTimeout(attemptImport, retryDelay); + return; + } + } + + reject(error); + } + }; + + attemptImport(); + }); + }, + { + ssr, + loading, + } + ); +} + +// Predefined safe dynamic imports for common components +export const SafeCustomEditor = createSafeDynamicImport( + () => import('@/components/editor/custom-editor'), + { ssr: false } +); + +export const SafeViewEditor = createSafeDynamicImport( + () => import('@/components/editor/view-editor'), + { ssr: false } +); + +export const SafeReactApexChart = createSafeDynamicImport( + () => import('react-apexcharts'), + { ssr: false } +); \ No newline at end of file