✨ Enhanced chunk error detection and automatic fallback to Supabase auth - Enhanced isClerkChunkError with specific CDN pattern matching (joint-cheetah-86.clerk.accounts.dev) - Added automatic Clerk disable when chunk loading fails - Implemented graceful fallback to Supabase authentication without interruption - Added user-friendly error messages and recovery UI - Created multi-layered error handling across ErrorBoundary, ClerkProvider, and global handlers - Added vite.config optimization for chunk loading with retry logic 🔧 Core improvements: - setupChunkErrorProtection() now activates immediately in main.tsx - Enhanced ClerkProvider with comprehensive error state handling - App.tsx ErrorBoundary detects and handles Clerk-specific chunk errors - Automatic sessionStorage flags for Clerk disable/skip functionality - URL parameter support for noClerk=true debugging 🚀 User experience: - Seamless transition from Clerk to Supabase when CDN fails - No app crashes or white screens during authentication failures - Automatic page refresh with fallback authentication system - Clear error messages explaining recovery process This resolves the ChunkLoadError: Loading chunk 344 failed from Clerk CDN and ensures the app remains functional with Supabase authentication fallback. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
137 lines
4.2 KiB
TypeScript
137 lines
4.2 KiB
TypeScript
import { createRoot } from "react-dom/client";
|
|
import { logger } from "@/utils/logger";
|
|
import { BrowserRouter } from "react-router-dom";
|
|
import { setupChunkErrorProtection } from "@/utils/chunkErrorProtection";
|
|
import App from "./App.tsx";
|
|
import "./index.css";
|
|
|
|
logger.info("main.tsx loaded");
|
|
|
|
// 청크 로딩 오류 보호 시스템 즉시 활성화
|
|
setupChunkErrorProtection();
|
|
|
|
// iOS 안전 영역 메타 태그 추가
|
|
const setViewportMetaTag = () => {
|
|
// 기존 viewport 메타 태그 찾기
|
|
let metaTag = document.querySelector('meta[name="viewport"]');
|
|
|
|
// 없으면 새로 생성
|
|
if (!metaTag) {
|
|
metaTag = document.createElement("meta");
|
|
metaTag.setAttribute("name", "viewport");
|
|
document.head.appendChild(metaTag);
|
|
}
|
|
|
|
// content 속성 설정 (viewport-fit=cover 추가)
|
|
metaTag.setAttribute(
|
|
"content",
|
|
"width=device-width, initial-scale=1.0, viewport-fit=cover"
|
|
);
|
|
};
|
|
|
|
// 메타 태그 설정 적용
|
|
setViewportMetaTag();
|
|
|
|
// 안전한 에러 화면 표시 함수
|
|
const showErrorScreen = (
|
|
title: string,
|
|
description: string,
|
|
details?: string
|
|
) => {
|
|
// React 렌더링과 충돌하지 않도록 안전하게 처리
|
|
setTimeout(() => {
|
|
const rootElement = document.getElementById("root");
|
|
if (rootElement && !rootElement.querySelector(".error-screen")) {
|
|
// 기존 내용을 보존하면서 에러 화면 추가
|
|
const errorDiv = document.createElement("div");
|
|
errorDiv.className = "error-screen";
|
|
errorDiv.style.cssText = `
|
|
position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 9999;
|
|
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
background: rgba(255, 255, 255, 0.95); font-family: Arial, sans-serif;
|
|
`;
|
|
|
|
errorDiv.innerHTML = `
|
|
<div style="color: red; font-size: 48px; margin-bottom: 20px;">⚠️</div>
|
|
<h1 style="margin-bottom: 20px;">${title}</h1>
|
|
<p style="margin-bottom: 20px; text-align: center;">${description}</p>
|
|
${details ? `<pre style="max-width: 80%; overflow: auto; background: #f5f5f5; padding: 10px; border-radius: 4px; margin-bottom: 20px;">${details}</pre>` : ""}
|
|
<button
|
|
onclick="window.location.reload()"
|
|
style="padding: 10px 20px; background-color: #3b82f6; color: white; border: none; border-radius: 4px; cursor: pointer;"
|
|
>
|
|
새로고침
|
|
</button>
|
|
`;
|
|
|
|
rootElement.appendChild(errorDiv);
|
|
}
|
|
}, 100);
|
|
};
|
|
|
|
// 전역 오류 핸들러 추가
|
|
window.onerror = function (message, source, lineno, colno, error) {
|
|
logger.error("전역 오류 발생:", { message, source, lineno, colno, error });
|
|
showErrorScreen(
|
|
"Zellyy Finance 오류",
|
|
"애플리케이션 로딩 중 오류가 발생했습니다.",
|
|
String(message)
|
|
);
|
|
return false;
|
|
};
|
|
|
|
// 처리되지 않은 Promise 오류 핸들러 추가
|
|
window.addEventListener("unhandledrejection", function (event) {
|
|
logger.error("처리되지 않은 Promise 오류:", event.reason);
|
|
showErrorScreen(
|
|
"Zellyy Finance 오류",
|
|
"비동기 작업 중 오류가 발생했습니다.",
|
|
String(event.reason)
|
|
);
|
|
});
|
|
|
|
// 디버깅 정보 출력
|
|
logger.info("환경 변수:", {
|
|
NODE_ENV: import.meta.env.MODE,
|
|
BASE_URL: import.meta.env.BASE_URL,
|
|
SUPABASE_URL: import.meta.env.VITE_SUPABASE_URL,
|
|
CLERK_PUBLISHABLE_KEY:
|
|
import.meta.env.VITE_CLERK_PUBLISHABLE_KEY?.substring(0, 20) + "...",
|
|
});
|
|
|
|
// 상태 확인
|
|
// TypeScript에서 window 객체에 사용자 정의 속성 추가
|
|
declare global {
|
|
interface Window {
|
|
supabaseEnabled: boolean;
|
|
}
|
|
}
|
|
|
|
// Supabase 활성화
|
|
window.supabaseEnabled = true;
|
|
|
|
try {
|
|
const rootElement = document.getElementById("root");
|
|
if (!rootElement) {
|
|
throw new Error("Root element not found");
|
|
}
|
|
|
|
// React root 생성 - DOM 직접 조작 제거
|
|
const root = createRoot(rootElement);
|
|
|
|
root.render(
|
|
<BrowserRouter>
|
|
<App />
|
|
</BrowserRouter>
|
|
);
|
|
|
|
logger.info("애플리케이션 렌더링 성공");
|
|
} catch (error) {
|
|
logger.error("애플리케이션 렌더링 오류:", error);
|
|
showErrorScreen(
|
|
"Zellyy Finance 오류",
|
|
"애플리케이션 로딩 중 오류가 발생했습니다.",
|
|
String(error)
|
|
);
|
|
}
|