/** * Playwright를 사용한 ChunkLoadError 상세 분석 스크립트 * * 브라우저를 자동화하여 콘솔 로그, 네트워크 요청, 오류를 상세히 캡처합니다. */ const { chromium } = require("playwright"); async function analyzeChunkLoadError() { console.log("🔍 ChunkLoadError 상세 분석 시작...\n"); const browser = await chromium.launch({ headless: false, // 실제 브라우저 창을 열어서 시각적으로 확인 devtools: true, // 개발자 도구 자동 열기 args: [ "--disable-web-security", "--disable-features=VizDisplayCompositor", "--no-sandbox", "--disable-dev-shm-usage", ], }); const context = await browser.newContext({ viewport: { width: 1280, height: 720 }, // 캐시 비활성화로 항상 최신 리소스 요청 ignoreHTTPSErrors: true, }); const page = await context.newPage(); // 이벤트 수집 배열 const consoleMessages = []; const networkRequests = []; const networkFailures = []; const errors = []; const chunkErrors = []; // 콘솔 메시지 캡처 page.on("console", (msg) => { const message = { type: msg.type(), text: msg.text(), location: msg.location(), timestamp: new Date().toISOString(), }; consoleMessages.push(message); // 실시간 콘솔 출력 const prefix = { error: "❌", warning: "⚠️ ", info: "ℹ️ ", log: "📝", }[msg.type()] || "📄"; console.log(`${prefix} [CONSOLE] ${msg.text()}`); }); // 네트워크 요청 모니터링 page.on("request", (request) => { const requestInfo = { url: request.url(), method: request.method(), headers: request.headers(), timestamp: new Date().toISOString(), resourceType: request.resourceType(), }; networkRequests.push(requestInfo); // Clerk 관련 요청만 출력 if (request.url().includes("clerk")) { console.log(`🌐 [REQUEST] ${request.method()} ${request.url()}`); } }); // 네트워크 응답 모니터링 page.on("response", (response) => { const isClerkRelated = response.url().includes("clerk"); const status = response.status(); if (isClerkRelated) { const statusIcon = status >= 400 ? "🔴" : status >= 300 ? "🟡" : "🟢"; console.log(`${statusIcon} [RESPONSE] ${status} ${response.url()}`); } // 실패한 응답 기록 if (status >= 400) { networkFailures.push({ url: response.url(), status: status, statusText: response.statusText(), headers: response.headers(), timestamp: new Date().toISOString(), }); } }); // 페이지 오류 캡처 page.on("pageerror", (error) => { const errorInfo = { name: error.name, message: error.message, stack: error.stack, timestamp: new Date().toISOString(), }; errors.push(errorInfo); // ChunkLoadError 특별 처리 if ( error.message.includes("Loading chunk") || error.name === "ChunkLoadError" ) { chunkErrors.push(errorInfo); console.log(`💥 [CHUNK ERROR] ${error.message}`); console.log(` Stack: ${error.stack?.split("\n")[0]}`); } else { console.log(`❌ [PAGE ERROR] ${error.name}: ${error.message}`); } }); // 네트워크 실패 처리 page.on("requestfailed", (request) => { const failure = { url: request.url(), method: request.method(), failure: request.failure()?.errorText, timestamp: new Date().toISOString(), }; networkFailures.push(failure); if (request.url().includes("clerk")) { console.log(`💔 [REQUEST FAILED] ${request.url()}`); console.log(` Error: ${request.failure()?.errorText}`); } }); try { console.log("🚀 페이지 로딩 시작..."); // 무한 새로고침 감지를 위한 네비게이션 카운터 let navigationCount = 0; page.on("framenavigated", () => { navigationCount++; console.log(`🔄 [NAVIGATION] 페이지 네비게이션 ${navigationCount}회`); if (navigationCount > 3) { console.log("⚠️ 무한 새로고침 감지됨!"); } }); // 페이지 로드 (타임아웃 30초로 단축) await page.goto("http://localhost:3002", { waitUntil: "domcontentloaded", // networkidle 대신 domcontentloaded 사용 timeout: 30000, }); console.log("✅ 페이지 로드 완료. 10초 대기 중..."); // 무한 새로고침 체크를 위해 10초 대기 await page.waitForTimeout(10000); console.log(`📊 총 네비게이션 횟수: ${navigationCount}회`); if (navigationCount > 3) { console.log("❌ 무한 새로고침 문제 발생"); chunkErrors.push({ name: "InfiniteRefresh", message: `무한 새로고침 감지: ${navigationCount}회 네비게이션`, timestamp: new Date().toISOString(), }); } // JavaScript 실행 상태 확인 const isJSWorking = await page.evaluate(() => { return ( typeof window.React !== "undefined" || document.querySelector("[data-reactroot]") !== null || document.querySelector("#root > *") !== null ); }); console.log(`🔧 JavaScript 실행 상태: ${isJSWorking ? "정상" : "실패"}`); // Clerk 로딩 상태 확인 const clerkStatus = await page.evaluate(() => { return { clerkLoaded: typeof window.Clerk !== "undefined", clerkProviderExists: document.querySelector("[data-clerk-provider]") !== null, clerkErrors: window.sessionStorage?.getItem("chunkLoadErrorMaxRetries"), skipClerk: window.sessionStorage?.getItem("skipClerk"), }; }); console.log("🔐 Clerk 상태:", JSON.stringify(clerkStatus, null, 2)); // 페이지 내용 확인 const pageContent = await page.evaluate(() => ({ title: document.title, hasContent: document.body.children.length > 1, rootContent: document.getElementById("root")?.children.length || 0, errorMessages: Array.from(document.querySelectorAll("*")) .filter( (el) => el.textContent?.includes("ChunkLoadError") || el.textContent?.includes("Loading chunk") || el.textContent?.includes("오류") ) .map((el) => el.textContent?.substring(0, 100)), })); console.log("📄 페이지 내용 분석:", JSON.stringify(pageContent, null, 2)); // 추가로 10초 더 대기하여 지연된 오류 캡처 console.log("⏳ 추가 10초 대기 중 (지연된 오류 캡처)..."); await page.waitForTimeout(10000); } catch (error) { console.log(`💥 페이지 로드 실패: ${error.message}`); } // 결과 분석 및 출력 console.log("\n" + "=".repeat(80)); console.log("📊 분석 결과 요약"); console.log("=".repeat(80)); console.log(`\n🔢 통계:`); console.log(` 콘솔 메시지: ${consoleMessages.length}개`); console.log(` 네트워크 요청: ${networkRequests.length}개`); console.log(` 네트워크 실패: ${networkFailures.length}개`); console.log(` 페이지 오류: ${errors.length}개`); console.log(` 청크 오류: ${chunkErrors.length}개`); if (chunkErrors.length > 0) { console.log(`\n💥 ChunkLoadError 상세 정보:`); chunkErrors.forEach((error, index) => { console.log(` ${index + 1}. ${error.message}`); console.log(` 시간: ${error.timestamp}`); if (error.stack) { console.log( ` 스택: ${error.stack.split("\n").slice(0, 3).join("\n ")}` ); } }); } if (networkFailures.filter((f) => f.url?.includes("clerk")).length > 0) { console.log(`\n💔 Clerk 관련 네트워크 실패:`); networkFailures .filter((f) => f.url?.includes("clerk")) .forEach((failure, index) => { console.log(` ${index + 1}. ${failure.url}`); console.log(` 오류: ${failure.failure || failure.status}`); console.log(` 시간: ${failure.timestamp}`); }); } // Clerk 관련 요청 분석 const clerkRequests = networkRequests.filter((req) => req.url.includes("clerk") ); if (clerkRequests.length > 0) { console.log(`\n🔐 Clerk 관련 요청 (${clerkRequests.length}개):`); clerkRequests.forEach((req, index) => { console.log(` ${index + 1}. ${req.method} ${req.url}`); }); } // 오류가 있는 콘솔 메시지 const errorMessages = consoleMessages.filter((msg) => msg.type === "error"); if (errorMessages.length > 0) { console.log(`\n❌ 콘솔 오류 메시지 (${errorMessages.length}개):`); errorMessages.forEach((msg, index) => { console.log(` ${index + 1}. ${msg.text}`); if (msg.location?.url) { console.log( ` 위치: ${msg.location.url}:${msg.location.lineNumber}` ); } }); } // 브라우저를 5초 더 열어둔 후 종료 console.log( "\n🔍 5초 후 브라우저를 닫습니다. 직접 확인하고 싶다면 Ctrl+C로 중단하세요." ); await page.waitForTimeout(5000); await browser.close(); console.log("\n✅ 분석 완료!"); // ChunkLoadError 해결 제안 if (chunkErrors.length > 0) { console.log("\n💡 ChunkLoadError 해결 제안:"); console.log(" 1. 개발 서버 재시작: npm run dev"); console.log(" 2. node_modules/.vite 캐시 삭제"); console.log(" 3. 브라우저 하드 새로고침: Ctrl+Shift+R"); console.log( " 4. Clerk 설정 확인: .env 파일의 VITE_CLERK_PUBLISHABLE_KEY" ); } } // 스크립트 실행 if (require.main === module) { analyzeChunkLoadError().catch(console.error); } module.exports = { analyzeChunkLoadError };