2025-12-08 10:02:33 +00:00
|
|
|
"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";
|
2025-12-09 01:51:07 +00:00
|
|
|
const REFRESH_TIMESTAMP_KEY = "chunk-load-error-timestamp";
|
2025-12-08 10:02:33 +00:00
|
|
|
|
2025-12-09 01:51:07 +00:00
|
|
|
// 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
|
|
|
|
|
};
|
|
|
|
|
|
2025-12-08 10:02:33 +00:00
|
|
|
// Handle unhandled promise rejections (for dynamic imports)
|
|
|
|
|
const handlePromiseRejection = (event: PromiseRejectionEvent) => {
|
|
|
|
|
const error = event.reason;
|
|
|
|
|
|
2025-12-09 01:51:07 +00:00
|
|
|
// 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
|
2025-12-08 10:02:33 +00:00
|
|
|
handleChunkLoadError();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Handle regular errors
|
|
|
|
|
const handleError = (event: ErrorEvent) => {
|
|
|
|
|
const error = event.error;
|
|
|
|
|
|
2025-12-09 01:51:07 +00:00
|
|
|
// 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
|
2025-12-08 10:02:33 +00:00
|
|
|
handleChunkLoadError();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleChunkLoadError = () => {
|
2025-12-09 01:51:07 +00:00
|
|
|
// Prevent multiple simultaneous refresh attempts
|
|
|
|
|
if (isRefreshing) {
|
|
|
|
|
console.log("Refresh already in progress, skipping...");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if we've already refreshed recently
|
2025-12-08 10:02:33 +00:00
|
|
|
const hasRefreshed = sessionStorage.getItem(REFRESH_KEY);
|
|
|
|
|
|
2025-12-09 01:51:07 +00:00
|
|
|
if (!hasRefreshed && shouldRefresh()) {
|
|
|
|
|
isRefreshing = true;
|
|
|
|
|
|
|
|
|
|
console.log("ChunkLoadError confirmed. Refreshing page in 100ms...");
|
2025-12-08 10:02:33 +00:00
|
|
|
|
|
|
|
|
// Mark that we've refreshed
|
|
|
|
|
sessionStorage.setItem(REFRESH_KEY, "true");
|
2025-12-09 01:51:07 +00:00
|
|
|
sessionStorage.setItem(REFRESH_TIMESTAMP_KEY, Date.now().toString());
|
2025-12-08 10:02:33 +00:00
|
|
|
|
2025-12-09 01:51:07 +00:00
|
|
|
// Small delay to allow logging and prevent immediate re-trigger
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
// Force reload the page (bypassing cache)
|
|
|
|
|
window.location.reload();
|
|
|
|
|
}, 100);
|
|
|
|
|
} else if (hasRefreshed) {
|
2025-12-08 10:02:33 +00:00
|
|
|
console.error("ChunkLoadError persists after refresh. There might be a deeper issue.");
|
2025-12-09 01:51:07 +00:00
|
|
|
console.error("Please try clearing your browser cache or contact support.");
|
2025-12-08 10:02:33 +00:00
|
|
|
|
2025-12-09 01:51:07 +00:00
|
|
|
// Clear the flag after 1 minute in case user navigates away and comes back
|
2025-12-08 10:02:33 +00:00
|
|
|
setTimeout(() => {
|
|
|
|
|
sessionStorage.removeItem(REFRESH_KEY);
|
2025-12-09 01:51:07 +00:00
|
|
|
sessionStorage.removeItem(REFRESH_TIMESTAMP_KEY);
|
|
|
|
|
}, 60000);
|
2025-12-08 10:02:33 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-12-09 01:51:07 +00:00
|
|
|
// 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();
|
|
|
|
|
|
2025-12-08 10:02:33 +00:00
|
|
|
// Add event listeners
|
2025-12-09 01:51:07 +00:00
|
|
|
window.addEventListener("error", handleError, true); // Use capture phase
|
2025-12-08 10:02:33 +00:00
|
|
|
window.addEventListener("unhandledrejection", handlePromiseRejection);
|
|
|
|
|
|
|
|
|
|
// Cleanup event listeners
|
|
|
|
|
return () => {
|
2025-12-09 01:51:07 +00:00
|
|
|
window.removeEventListener("error", handleError, true);
|
2025-12-08 10:02:33 +00:00
|
|
|
window.removeEventListener("unhandledrejection", handlePromiseRejection);
|
|
|
|
|
};
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
// This component doesn't render anything
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|