feat(WEB): 에러 리포팅 시스템 및 ErrorBoundary 개선

- 에러 리포팅 유틸리티 및 API route 추가
- ErrorBoundary(error.tsx) 에러 리포팅 연동
- providers 컴포넌트 구조 추가
- layout에 provider 적용

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-02-12 17:04:42 +09:00
parent 3aeaaa76a8
commit 31be9d4a25
7 changed files with 331 additions and 3 deletions

View File

@@ -0,0 +1,68 @@
'use client';
import { useEffect } from 'react';
import { reportErrorToSlack } from '@/lib/error-reporting';
/**
* ChunkLoadError 전역 감지 컴포넌트
*
* 배포 후 캐시된 구버전 JS 청크 로딩 실패를 감지하여 Slack으로 전송
* - window 'error' 이벤트: <script> 태그 로딩 실패
* - window 'unhandledrejection': dynamic import() 실패
*/
const CHUNK_ERROR_PATTERNS = [
'Loading chunk',
'ChunkLoadError',
'Failed to fetch dynamically imported module',
'error loading dynamically imported module',
];
function isChunkError(message: string): boolean {
return CHUNK_ERROR_PATTERNS.some((pattern) =>
message.toLowerCase().includes(pattern.toLowerCase())
);
}
function sendReport(message: string, stack?: string) {
reportErrorToSlack({
errorType: 'CHUNK_LOAD_ERROR',
message,
pageUrl: window.location.href,
timestamp: new Date().toISOString(),
stack: stack?.slice(0, 500),
userAgent: navigator.userAgent,
});
}
export function ChunkErrorHandler() {
useEffect(() => {
function handleError(event: ErrorEvent) {
if (event.message && isChunkError(event.message)) {
sendReport(event.message, event.error?.stack);
}
}
function handleRejection(event: PromiseRejectionEvent) {
const reason = event.reason;
const message =
reason instanceof Error ? reason.message : String(reason ?? '');
if (isChunkError(message)) {
sendReport(
message,
reason instanceof Error ? reason.stack?.slice(0, 500) : undefined
);
}
}
window.addEventListener('error', handleError);
window.addEventListener('unhandledrejection', handleRejection);
return () => {
window.removeEventListener('error', handleError);
window.removeEventListener('unhandledrejection', handleRejection);
};
}, []);
return null;
}