feat: update bug fixing chunk

This commit is contained in:
hanif salafi 2025-07-03 21:35:45 +07:00
parent cabd73822f
commit af9000498a
7 changed files with 325 additions and 8 deletions

View File

@ -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 (
<html lang="en" suppressHydrationWarning>
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`} suppressHydrationWarning>
{children}
<ChunkErrorBoundary>
{children}
</ChunkErrorBoundary>
</body>
</html>
);

View File

@ -250,7 +250,11 @@ export default function LatestandPopular() {
<div key={index}>
<div className="relative w-full aspect-video mb-3">
<Image
src={article.thumbnailUrl}
src={
articles[0]?.thumbnailUrl ||
articles[0]?.files?.[0]?.file_url ||
"/default-image.jpg"
}
alt={"article.title"}
fill
sizes="(max-width: 1024px) 100vw, 33vw"
@ -407,7 +411,11 @@ export default function LatestandPopular() {
<div key={index} className="flex gap-3">
<div className="relative w-[120px] h-[86px] shrink-0">
<Image
src={article?.thumbnailUrl}
src={
articles[0]?.thumbnailUrl ||
articles[0]?.files?.[0]?.file_url ||
"/default-image.jpg"
}
alt={"article?.title"}
fill
className="object-cover"

View File

@ -0,0 +1,104 @@
"use client";
import React, { Component, ErrorInfo, ReactNode } from 'react';
import { Button } from '@/components/ui/button';
import { RefreshCw } from 'lucide-react';
interface Props {
children: ReactNode;
fallback?: ReactNode;
}
interface State {
hasError: boolean;
error?: Error;
}
class ChunkErrorBoundary extends Component<Props, State> {
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 (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-50 via-white to-slate-50">
<div className="text-center p-8 max-w-md">
<div className="mb-6">
<div className="w-16 h-16 bg-red-100 rounded-full flex items-center justify-center mx-auto mb-4">
<RefreshCw className="w-8 h-8 text-red-600" />
</div>
<h2 className="text-xl font-semibold text-gray-900 mb-2">
Chunk Loading Error
</h2>
<p className="text-gray-600 mb-6">
There was an issue loading a part of the application. This usually happens when the application has been updated.
</p>
</div>
<div className="space-y-3">
<Button
onClick={this.handleRetry}
className="w-full bg-blue-600 hover:bg-blue-700 text-white"
>
<RefreshCw className="w-4 h-4 mr-2" />
Reload Application
</Button>
<Button
onClick={() => window.location.href = '/'}
variant="outline"
className="w-full"
>
Go to Homepage
</Button>
</div>
{process.env.NODE_ENV === 'development' && this.state.error && (
<details className="mt-6 text-left">
<summary className="cursor-pointer text-sm text-gray-500 hover:text-gray-700">
Error Details (Development)
</summary>
<pre className="mt-2 p-3 bg-gray-100 rounded text-xs overflow-auto">
{this.state.error.message}
</pre>
</details>
)}
</div>
</div>
);
}
return this.props.children;
}
}
export default ChunkErrorBoundary;

View File

@ -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<number[]>([]);
const [seriesView, setSeriesView] = useState<number[]>([]);
const [seriesShare, setSeriesShare] = useState<number[]>([]);
const ReactApexChart = dynamic(() => import("react-apexcharts"), {
ssr: false,
});
useEffect(() => {
initFetch();
@ -132,7 +129,7 @@ const ApexChartColumn = (props: {
return (
<div className="h-full">
<div id="chart" className="h-full">
<ReactApexChart
<SafeReactApexChart
options={{
chart: {
height: "100%",

View File

@ -10,6 +10,53 @@ const nextConfig: NextConfig = {
eslint: {
ignoreDuringBuilds: true,
},
webpack: (config, { isServer }) => {
// 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;

View File

@ -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 = `
<div style="display: flex; align-items: center; gap: 8px;">
<span></span>
<span>Application update detected. Please refresh the page.</span>
</div>
<button onclick="window.location.reload()" style="
margin-top: 8px;
background: #dc2626;
color: white;
border: none;
padding: 4px 8px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
">Refresh</button>
`;
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();
}

70
utils/dynamic-import.ts Normal file
View File

@ -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<T extends ComponentType<any>>(
importFn: () => Promise<{ default: T }>,
options: DynamicImportOptions = {}
) {
const { ssr = false, loading, retries = 3, retryDelay = 1000 } = options;
return dynamic(
() => {
return new Promise<T>((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 }
);