에러 경계 vs 전역 에러 핸들러: AI 코딩 도구로 만든 웹앱의 안정성, 어디서 잡을까
React 에러 경계와 전역 에러 핸들러의 차이를 명확히 하고, AI 코딩 도구로 만든 웹앱에서 각각을 언제 어떻게 적용할지 실전 가이드를 제공합니다.
에러 경계와 전역 핸들러, 왜 둘 다 필요할까?
AI 코딩 도구로 빠르게 만든 웹앱일수록 예기치 않은 에러에 취약하다. 컴포넌트 렌더링 중 에러가 터지면 전체 앱이 하얀 화면(White Screen of Death)으로 변할 수 있고, API 호출 실패나 네트워크 에러는 조용히 사용자 경험을 깨먹는다.
문제는 에러가 발생하는 위치와 종류가 다르다는 것이다. React 컴포넌트 렌더링 에러, 비동기 작업 에러, 네트워크 요청 에러—각각 다른 방식으로 처리해야 한다. 에러 경계(Error Boundary)와 전역 에러 핸들러는 이 두 가지 문제를 각각 담당하는 방어선이다.
에러 경계: 렌더링 에러의 마지막 보루
에러 경계는 React 클래스 컴포넌트에서만 구현할 수 있는 특수한 메커니즘이다. 자식 컴포넌트 렌더링 중 발생한 JavaScript 에러를 '잡아서' 앱 전체 충돌을 방지한다.
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('에러 발생:', error, errorInfo);
// 로깅 서비스에 전송 가능
}
render() {
if (this.state.hasError) {
return <div>문제가 발생했습니다. 페이지를 새로고침해주세요.</div>;
}
return this.props.children;
}
}
에러 경계가 잡을 수 있는 것: 렌더링 중 에러, 라이프사이클 메서드 에러, 생성자 에러.
에러 경계가 잡을 수 없는 것: 이벤트 핸들러 에러, 비동기 코드(setTimeout, Promise), 서버 사이드 렌더링.
예를 들어 사용자 프로필을 표시하는 컴포넌트에서 user.profile.name을 접근하는데 user가 undefined라면, 에러 경계가 이를 잡아서 폴백 UI를 보여준다.
전역 에러 핸들러: 비동기와 이벤트의 그물
전역 에러 핸들러는 에러 경계가 잡지 못하는 영역을 담당한다. 주로 두 가지 방식이다.
1. window.onerror — 동기 에러 캐치
window.onerror = function(message, source, lineno, colno, error) {
console.error('전역 에러:', message);
// 에러 로깅 서비스로 전송
return true; // 에러 전파 중지
};
2. window.onunhandledrejection — Promise 거부 캐치
window.onunhandledrejection = function(event) {
console.error('처리되지 않은 Promise 거부:', event.reason);
event.preventDefault(); // 에러 전파 중지
};
API 호출이 실패했을 때를 생각해보자. fetch나 axios 요청에서 .catch()를 빼먹으면 Promise 거부가 발생한다. 전역 핸들러가 이를 감지해서 사용자에게 "네트워크 오류가 발생했습니다"라는 토스트 메시지를 보여줄 수 있다.
실전: 언제 어디에 배치할까?
에러 경계를 쓸 때:
- 페이지 섹션 단위로 격리하고 싶을 때: 헤더, 사이드바, 메인 콘텐츠 각각에 에러 경계를 감싼다.
- 서드파티 라이브러리가 불안정할 때: 차트, 데이터 테이블 같은 외부 컴포넌트를 에러 경계로 보호한다.
- 사용자가 계속 앱을 쓸 수 있게 하고 싶을 때: 한 섹션의 에러가 다른 섹션을 깨뜨리지 않게 한다.
전역 핸들러를 쓸 때:
- API 에러를 일관되게 처리하고 싶을 때: 모든 비동기 요청의 실패를 한 곳에서 관리한다.
- 에러 로깅 서비스로 보낼 때: Sentry, LogRocket 같은 도구는 전역 핸들러로 잡히는 에러까지 수집하며, 직접 만든 핸들러에서 이런 서비스로 에러를 보낼 수도 있다.
- 사용자에게 알림을 보낼 때: 에러 토스트, 모달 등을 중앙에서 트리거한다.
실제 구조 예시:
// App.jsx
<ErrorBoundary>
<Header />
<ErrorBoundary>
<MainContent />
</ErrorBoundary>
<Footer />
</ErrorBoundary>
// 전역 핸들러는 index.js 최상단에서 한 번만 설정
window.onunhandledrejection = handleGlobalError;
AI 코딩 도구 사용 시 주의점
Cursor나 Claude Code로 코드를 생성받을 때, 에러 처리 로직이 누락되기 쉽다. 생성된 컴포넌트를 받았다면:
- API 호출 부분 점검:
.catch()또는try-catch가 있는가? - 비동기 상태 관리: 로딩, 에러, 성공 상태를 모두 처리하는가?
- 에러 경계 래핑: 서드파티 라이브러리나 복잡한 컴포넌트는 에러 경계로 감싸졌는가?
AI가 생성한 코드는 "정상 흐름"에만 최적화되어 있으므로, 에러 처리는 수동으로 검토하고 추가해야 한다.
에러 경계와 전역 핸들러를 함께 쓸 때 주의할 점은?
두 메커니즘이 겹칠 수 있다. 예를 들어 이벤트 핸들러 내에서 에러가 발생하면 에러 경계는 잡지 못하고 전역 핸들러만 작동한다. 따라서 이벤트 핸들러 내부에는 항상 try-catch를 쓰는 것이 안전하다. 에러 경계와 전역 핸들러는 보완 관계이지, 하나가 다른 하나를 대체하지 않는다.
프로덕션 환경에서 에러를 어떻게 모니터링하나요?
Sentry, LogRocket, Datadog 같은 에러 추적 서비스를 쓴다. 전역 핸들러에서 에러를 감지하면 이들 서비스로 자동 전송하도록 설정하면 된다. 사용자가 겪은 에러를 실시간으로 모니터링할 수 있고, 스택 트레이스와 사용자 세션 정보까지 함께 기록되므로 디버깅이 훨씬 빨라진다.
에러 경계 없이 전역 핸들러만으로는 충분하지 않나요?
충분하지 않다. 전역 핸들러는 렌더링 에러를 기록할 수는 있어도 화면을 복구하지는 못한다. 컴포넌트가 렌더링 중에 에러가 나면 전역 핸들러가 작동해도 하얀 화면만 남는다. 폴백 UI를 보여줄 수 있는 것은 에러 경계이므로, 둘 다 있어야 웹앱의 안정성이 높아진다.
결론: 두 겹의 방어선
에러 경계는 컴포넌트 렌더링 영역을, 전역 핸들러는 비동기·이벤트 영역을 담당한다. AI 코딩 도구로 빠르게 프로토타입을 만들었다면, 이 두 가지 에러 처리 메커니즘을 반드시 추가해야 한다. 특히 API 호출이 많은 앱이라면 전역 핸들러 없이는 불안정하고, 복잡한 UI 컴포넌트가 많다면 에러 경계 없이도 위험하다.
만든 웹앱의 안정성을 높이고 싶다면, 지금 바로 에러 경계와 전역 핸들러를 추가하고, 실제 사용자 환경에서 어떤 에러가 발생하는지 모니터링해보자. 그 과정에서 발견한 개선점들을 Vibeollio에 프로젝트 등록해서 다른 개발자들의 피드백을 받으면, 더 견고한 웹앱으로 발전시킬 수 있다.
