"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"; const REFRESH_TIMESTAMP_KEY = "chunk-load-error-timestamp"; // Flag to prevent multiple simultaneous refresh attempts let isRefreshing = false; /** * Check if error is actually a ChunkLoadError * This is more specific to avoid false positives */ const isChunkLoadError = (error: any, message?: string): boolean => { // Direct ChunkLoadError check if (error?.name === "ChunkLoadError") return true; // Check for specific chunk loading patterns (for webpack/next.js) const errorMessage = error?.message || message || ""; const isChunkError = errorMessage.includes("Loading chunk") || errorMessage.includes("ChunkLoadError") || (errorMessage.includes("Failed to fetch") && errorMessage.includes("_next/static")) || (errorMessage.includes("dynamically imported module") && errorMessage.includes("_next")); // Exclude normal network errors or API errors const isNotApiError = !errorMessage.includes("/api/") && !errorMessage.includes("http") && !errorMessage.includes("https") && !errorMessage.includes("service/") && !errorMessage.includes("endpoint"); return isChunkError && isNotApiError; }; /** * Check if we should refresh based on time * Prevent refresh if last refresh was less than 5 seconds ago */ const shouldRefresh = (): boolean => { const lastRefreshTime = sessionStorage.getItem(REFRESH_TIMESTAMP_KEY); if (!lastRefreshTime) return true; const timeSinceLastRefresh = Date.now() - parseInt(lastRefreshTime, 10); return timeSinceLastRefresh > 5000; // 5 seconds }; // Handle unhandled promise rejections (for dynamic imports) const handlePromiseRejection = (event: PromiseRejectionEvent) => { const error = event.reason; // Only handle if it's actually a ChunkLoadError if (isChunkLoadError(error)) { console.warn("ChunkLoadError detected in promise rejection:", error); event.preventDefault(); // Prevent default error handling handleChunkLoadError(); } }; // Handle regular errors const handleError = (event: ErrorEvent) => { const error = event.error; // Only handle if it's actually a ChunkLoadError if (isChunkLoadError(error, event.message)) { console.warn("ChunkLoadError detected in error event:", error); event.preventDefault(); // Prevent default error handling handleChunkLoadError(); } }; const handleChunkLoadError = () => { // Prevent multiple simultaneous refresh attempts if (isRefreshing) { console.log("Refresh already in progress, skipping..."); return; } // Check if we've already refreshed recently const hasRefreshed = sessionStorage.getItem(REFRESH_KEY); if (!hasRefreshed && shouldRefresh()) { isRefreshing = true; console.log("ChunkLoadError confirmed. Refreshing page in 100ms..."); // Mark that we've refreshed sessionStorage.setItem(REFRESH_KEY, "true"); sessionStorage.setItem(REFRESH_TIMESTAMP_KEY, Date.now().toString()); // Small delay to allow logging and prevent immediate re-trigger setTimeout(() => { // Force reload the page (bypassing cache) window.location.reload(); }, 100); } else if (hasRefreshed) { console.error("ChunkLoadError persists after refresh. There might be a deeper issue."); console.error("Please try clearing your browser cache or contact support."); // Clear the flag after 1 minute in case user navigates away and comes back setTimeout(() => { sessionStorage.removeItem(REFRESH_KEY); sessionStorage.removeItem(REFRESH_TIMESTAMP_KEY); }, 60000); } }; // Clear old refresh flags on component mount (for new session) const clearOldFlags = () => { const lastRefreshTime = sessionStorage.getItem(REFRESH_TIMESTAMP_KEY); if (lastRefreshTime) { const timeSinceLastRefresh = Date.now() - parseInt(lastRefreshTime, 10); // Clear if last refresh was more than 5 minutes ago if (timeSinceLastRefresh > 300000) { sessionStorage.removeItem(REFRESH_KEY); sessionStorage.removeItem(REFRESH_TIMESTAMP_KEY); } } }; clearOldFlags(); // Add event listeners window.addEventListener("error", handleError, true); // Use capture phase window.addEventListener("unhandledrejection", handlePromiseRejection); // Cleanup event listeners return () => { window.removeEventListener("error", handleError, true); window.removeEventListener("unhandledrejection", handlePromiseRejection); }; }, []); // This component doesn't render anything return null; }