React Error Boundary 완벽 가이드
"예상치 못한 에러 하나가 앱 전체를 하얀 화면으로 만들어버린 경험, 다들 있으시죠?"
1. 왜 Error Boundary가 필요한가
React 16 이전에는 렌더링 중 하나의 컴포넌트에서 에러가 발생하면 React의 내부 상태가 손상되어, 이후 렌더링에서 예측 불가능한 결과를 냈습니다. React 16부터는 렌더링 도중 처리되지 않은 에러가 발생하면 컴포넌트 트리 전체를 언마운트하도록 정책이 바뀌었습니다.
즉, 아무 처리도 하지 않으면 다음과 같은 일이 벌어집니다.
댓글 위젯 하나가 에러를 던짐
↓
React가 컴포넌트 트리 전체를 unmount
↓
사용자는 완전히 하얀 화면(White Screen of Death)을 마주함
이 문제를 해결하기 위해 등장한 것이 바로 Error Boundary입니다.
2. Error Boundary란 무엇인가
Error Boundary는 자신의 하위 트리에서 발생하는 JavaScript 에러를 캐치하고, 에러를 기록하며, 깨진 화면 대신 폴백(fallback) UI를 보여주는 React 컴포넌트입니다.
핵심은 이것이 일반적인 try/catch처럼 동작하지 않는다는 점입니다. try/catch는 명령형 코드의 실행 흐름을 감싸지만, Error Boundary는 선언적으로 컴포넌트 트리의 렌더링 흐름을 감쌉니다.
<ErrorBoundary> ← 에러를 잡는 경계
<Header />
<ErrorBoundary> ← 더 세밀한 경계도 가능
<CommentWidget /> ← 여기서 에러 발생 시
</ErrorBoundary> ← 이 안에서만 격리됨
<Footer /> ← Footer는 영향받지 않음
</ErrorBoundary>
3. 동작 원리: 두 개의 생명주기 메서드
Error Boundary는 다음 두 생명주기 메서드 중 하나 이상을 정의한 클래스 컴포넌트로만 만들 수 있습니다.
메서드 역할 실행 시점
| static getDerivedStateFromError(error) | 폴백 UI를 렌더링하기 위해 state를 갱신 | 렌더링 단계 (side effect 금지) |
| componentDidCatch(error, info) | 에러를 로깅하기 위한 side effect 수행 | 커밋 단계 |
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
// 렌더링 단계 — 다음 렌더링에서 폴백 UI를 보여주기 위해 state만 갱신
static getDerivedStateFromError(error) {
return { hasError: true };
}
// 커밋 단계 — 실제 로깅, 리포팅 같은 side effect는 여기서
componentDidCatch(error, errorInfo) {
console.error('ErrorBoundary caught:', error, errorInfo);
// logErrorToService(error, errorInfo);
}
render() {
if (this.state.hasError) {
return this.props.fallback ?? <h2>문제가 발생했습니다.</h2>;
}
return this.props.children;
}
}
두 메서드를 분리해둔 이유는 명확합니다. getDerivedStateFromError는 순수해야 하는 렌더링 단계에서 실행되므로 side effect(네트워크 요청, 로깅 등)를 넣으면 안 되고, 실제 side effect는 componentDidCatch에서 처리해야 합니다.
4. 실전 사용 예시
function App() {
return (
<ErrorBoundary fallback={<ErrorFallback />}>
<Dashboard />
</ErrorBoundary>
);
}
function ErrorFallback() {
return (
<div role="alert" className="error-fallback">
<h2>일시적인 오류가 발생했습니다</h2>
<p>잠시 후 다시 시도해주세요.</p>
</div>
);
}
재시도 기능 추가하기
단순히 에러를 보여주는 것을 넘어, 재시도(retry) 가 가능하도록 설계하면 UX가 크게 개선됩니다.
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error, info) {
this.props.onError?.(error, info);
}
handleReset = () => {
this.setState({ hasError: false });
};
render() {
if (this.state.hasError) {
return (
<div role="alert">
<p>문제가 발생했어요.</p>
<button onClick={this.handleReset}>다시 시도</button>
</div>
);
}
return this.props.children;
}
}
⚠️ 다만 재시도가 만능은 아닙니다. 에러의 원인(예: 잘못된 API 응답 스키마)이 그대로라면 재시도해도 같은 에러가 반복될 수 있으므로, key prop을 바꿔 하위 트리를 강제로 리마운트시키는 패턴을 함께 쓰는 경우가 많습니다.
5. Error Boundary가 잡지 못하는 에러들
이 부분이 실무에서 가장 많이 간과되는 지점입니다. Error Boundary는 렌더링 중 발생하는 에러만 잡습니다. 아래 상황들은 잡히지 않습니다.
상황 이유 대안
| 이벤트 핸들러 내부 에러 | React 렌더링 흐름 바깥이므로 | 핸들러 안에서 직접 try/catch |
| 비동기 코드 (setTimeout, fetch 콜백 등) | 렌더링 단계와 무관한 타이밍에 실행 | try/catch + 상태로 UI 제어 |
| 서버 사이드 렌더링 중 발생하는 에러 | Error Boundary는 클라이언트 렌더링 트리 기준 | 서버 측 에러 핸들링 별도 구현 |
| Error Boundary 자기 자신의 에러 | 자기 하위 트리만 감시 가능 | 상위에 별도의 Error Boundary 배치 |
// ❌ 잡히지 않음 — 이벤트 핸들러 내부
function Button() {
const handleClick = () => {
throw new Error('클릭 에러'); // ErrorBoundary가 못 잡음
};
return <button onClick={handleClick}>Click</button>;
}
// ✅ 직접 처리해야 함
function Button() {
const handleClick = () => {
try {
riskyOperation();
} catch (error) {
reportError(error);
}
};
return <button onClick={handleClick}>Click</button>;
}
6. 함수형 컴포넌트에는 왜 없을까?
React 팀은 함수형 컴포넌트를 위한 useErrorBoundary 같은 공식 훅을 아직 제공하지 않습니다. getDerivedStateFromError와 componentDidCatch는 클래스 컴포넌트 API로만 존재하기 때문에, 함수형 컴포넌트만 쓰는 코드베이스에서도 Error Boundary 자체는 클래스로 만들 수밖에 없습니다.
이 간극을 메우기 위해 커뮤니티에서 널리 쓰이는 라이브러리가 바로 react-error-boundary 입니다.
npm install react-error-boundary
import { ErrorBoundary } from 'react-error-boundary';
function Fallback({ error, resetErrorBoundary }) {
return (
<div role="alert">
<p>에러: {error.message}</p>
<button onClick={resetErrorBoundary}>다시 시도</button>
</div>
);
}
function App() {
return (
<ErrorBoundary
FallbackComponent={Fallback}
onError={(error, info) => logErrorToService(error, info)}
onReset={() => window.location.reload()}
>
<Dashboard />
</ErrorBoundary>
);
}
이 라이브러리는 추가로 useErrorBoundary 훅을 제공해, 비동기 코드나 이벤트 핸들러에서 발생한 에러를 의도적으로 가장 가까운 Error Boundary에 위임할 수 있게 해줍니다.
import { useErrorBoundary } from 'react-error-boundary';
function DataFetcher() {
const { showBoundary } = useErrorBoundary();
useEffect(() => {
fetchData().catch((error) => showBoundary(error));
}, []);
return <div>...</div>;
}
7. 실전 배치 전략
Error Boundary를 어디에 배치하느냐가 UX를 좌우합니다. 크게 세 가지 전략이 있습니다.
(1) 최상위 하나만 두는 전략
가장 간단하지만, 앱 어디서든 에러가 나면 전체 화면이 폴백 UI로 대체됩니다.
(2) 라우트 단위로 배치
<ErrorBoundary>
<Router>
<Route path="/dashboard" element={
<ErrorBoundary><Dashboard /></ErrorBoundary>
} />
<Route path="/settings" element={
<ErrorBoundary><Settings /></ErrorBoundary>
} />
</Router>
</ErrorBoundary>
한 페이지에서 에러가 나도 다른 페이지 이동이나 네비게이션은 살아있습니다.
(3) 위젯 단위 세분화 (권장)
독립적으로 실패해도 무방한 단위(광고 배너, 추천 위젯, 댓글 영역 등)마다 개별 Error Boundary를 씌우면, 한 위젯의 에러가 전체 페이지 경험을 해치지 않습니다.
<Page>
<ErrorBoundary fallback={<AdFallback />}><AdBanner /></ErrorBoundary>
<MainContent />
<ErrorBoundary fallback={<CommentsFallback />}><Comments /></ErrorBoundary>
</Page>
💡 실무 팁: 핵심 비즈니스 로직(결제, 로그인 등)은 상위 레벨에서 크게 감싸고, 부가 기능(추천, 광고, 위젯)은 잘게 쪼개 감싸는 것이 일반적인 패턴입니다.
8. 에러 리포팅 연동하기
componentDidCatch나 onError 콜백은 Sentry, Datadog 같은 모니터링 서비스와 연동하기에 최적의 지점입니다.
<ErrorBoundary
FallbackComponent={Fallback}
onError={(error, info) => {
Sentry.captureException(error, {
contexts: { react: { componentStack: info.componentStack } },
});
}}
>
<App />
</ErrorBoundary>
이렇게 하면 사용자에게는 부드러운 폴백 UI를 보여주면서, 개발팀은 실제 프로덕션 에러를 놓치지 않고 추적할 수 있습니다.
9. 체크리스트로 정리하기
- [ ] 앱 최상위에 전역 Error Boundary를 둔다
- [ ] 라우트 또는 주요 위젯 단위로 세분화된 Error Boundary를 추가한다
- [ ] componentDidCatch / onError를 에러 모니터링 서비스와 연동한다
- [ ] 이벤트 핸들러, 비동기 코드는 별도로 try/catch 처리한다
- [ ] 폴백 UI에 재시도 버튼 또는 명확한 안내 문구를 제공한다
- [ ] 클래스 컴포넌트 작성이 번거롭다면 react-error-boundary 라이브러리를 도입한다
10. 마무리
Error Boundary는 화려한 기능은 아니지만, 프로덕션 환경에서 사용자가 마주치는 최악의 경험(하얀 화면)을 방지하는 가장 확실한 안전장치입니다. 완벽하게 에러 없는 코드를 짜는 것은 불가능에 가깝지만, 에러가 나더라도 우아하게 무너지는 UI를 설계하는 것은 충분히 가능합니다.
작은 위젯 단위로 Error Boundary를 세분화하고, 모니터링 서비스와 연동해두는 것만으로도 서비스의 안정성과 사용자 경험은 눈에 띄게 달라집니다.
이 글이 도움이 되셨다면, 다음에는 react-error-boundary의 내부 구현을 뜯어보는 글로 찾아뵙겠습니다.
'Frontend' 카테고리의 다른 글
| [Frontend] useTransition과 Error Boundary가 만나면? (0) | 2026.07.14 |
|---|---|
| [Frontend] React Error Boundary 뜯어보기 (0) | 2026.07.14 |
| [Frontend] React key, 그리고 인덱스를 key로 쓰면 생기는 일 (0) | 2026.07.09 |
| [Frontend] LESS 관리하기 (0) | 2026.07.09 |
| [Frontend] Typescript는 빌드할 때, 무슨 일을 하는가? (0) | 2026.07.09 |