feat: Implement comprehensive Clerk ChunkLoadError recovery system

 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>
This commit is contained in:
hansoo
2025-07-14 10:36:37 +09:00
parent 0409fcf7f1
commit a96f776157
6 changed files with 175 additions and 43 deletions

View File

@@ -27,7 +27,12 @@ import {
createLazyComponent,
resetChunkRetryFlags,
} from "./utils/lazyWithRetry";
import { setupChunkErrorProtection } from "./utils/chunkErrorProtection";
import {
setupChunkErrorProtection,
isChunkLoadError,
isClerkChunkError,
handleChunkLoadError,
} from "./utils/chunkErrorProtection";
import {
ClerkProvider,
ClerkDebugInfo,
@@ -111,13 +116,67 @@ class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
logger.error("애플리케이션 오류:", error, errorInfo);
// Sentry에 에러 리포팅
// ChunkLoadError 처리
if (isChunkLoadError(error)) {
if (isClerkChunkError(error)) {
logger.warn("Error Boundary에서 Clerk 청크 오류 감지. 자동 복구 시도");
// Clerk 자동 비활성화
sessionStorage.setItem("disableClerk", "true");
// 3초 후 새로고침
setTimeout(() => {
const url = new URL(window.location.href);
url.searchParams.set("noClerk", "true");
url.searchParams.set("_t", Date.now().toString());
window.location.href = url.toString();
}, 3000);
return;
} else {
// 일반 청크 오류 처리
handleChunkLoadError(error);
return;
}
}
// Sentry에 에러 리포팅 (청크 오류가 아닌 경우만)
captureError(error, { errorInfo });
}
render(): ReactNode {
if (this.state.hasError) {
// 오류 발생 시 대체 UI 표시
// ChunkLoadError인 경우 특별한 UI 표시
if (this.state.error && isChunkLoadError(this.state.error)) {
const isClerkError = isClerkChunkError(this.state.error);
return (
<div className="flex flex-col items-center justify-center min-h-screen p-4 text-center">
<div className={`text-4xl mb-4 ${isClerkError ? "🔧" : "⚠️"}`}>
{isClerkError ? "🔧" : "⚠️"}
</div>
<h2 className="text-xl font-bold mb-4">
{isClerkError ? "Clerk 로딩 오류" : "앱 로딩 오류"}
</h2>
<p className="mb-4 text-gray-600">
{isClerkError
? "Supabase 인증으로 자동 전환 중입니다. 잠시만 기다려주세요..."
: "앱을 복구하고 있습니다. 잠시만 기다려주세요..."}
</p>
{!isClerkError && (
<button
onClick={() => {
sessionStorage.clear();
window.location.reload();
}}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
</button>
)}
</div>
);
}
// 일반 오류 처리
return (
this.props.fallback || (
<div className="flex flex-col items-center justify-center min-h-screen p-4 text-center">