프로덕션에서 갑작스런 빈 화면을 막고 사용자에게 의미 있는 대체 UI를 보여주려면 Error Boundary가 필수입니다. Error Boundary는 하위 트리 렌더링 중 발생한 오류를 잡아 폴백 UI로 전환하고, 로깅까지 연결할 수 있는 안전망입니다.
1. Error Boundary가 필요한 이유
- 컴포넌트 렌더/라이프사이클에서 발생한 오류로 전체 앱이 깨지는 것을 방지합니다.
- 사용자에게 폴백 UI(다시 시도, 고객센터 안내 등)를 즉시 제공합니다.
- 모니터링/로그 수집 도구(Sentry 등)와 연동해 장애 탐지 속도를 높입니다.
2. 기본 구현 (Class 기반)
import React from 'react';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, info) {
// 여기에 로깅/모니터링 연동
// logError(error, info);
console.error('ErrorBoundary caught:', error, info);
}
handleReset = () => {
this.setState({ hasError: false, error: null });
this.props.onReset?.();
};
render() {
if (this.state.hasError) {
return (
문제가 발생했습니다.
{String(this.state.error)}
);
}
return this.props.children;
}
}
export default ErrorBoundary;3. 어디에 감쌀까요?
- 전체 앱 루트: 치명적 오류에 대한 최후의 안전망
- 페이지/라우트 단위: 문제 구역만 격리
- 외부 의존도가 높은 위젯: 그래프, 광고, 서드파티 SDK
// 루트 또는 라우트 경계 예시
<ErrorBoundary onReset={() => window.location.reload()}>
<App />
</ErrorBoundary>4. Error Boundary가 잡지 못하는 오류
- 이벤트 핸들러 내부 오류
- 비동기 콜백/Promise 내부 오류
- 서버사이드 렌더링(SSR) 중 발생한 오류
이 경우 패턴을 통해 경계로 전달해야 합니다.
5. 비동기/이벤트 오류를 경계로 전달하는 패턴
import React from 'react';
function FetchUser({ id }) {
const [data, setData] = React.useState(null);
const [error, setError] = React.useState(null);
React.useEffect(() => {
let cancelled = false;
(async () => {
try {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error('사용자 정보를 불러오지 못했습니다');
const json = await res.json();
if (!cancelled) setData(json);
} catch (e) {
if (!cancelled) setError(e);
}
})();
return () => { cancelled = true; };
}, [id]);
// Error Boundary로 승격
if (error) throw error;
if (!data) return <div>로딩...</div>;
return <div>{data.name}</div>;
}
function DangerousButton() {
const [error, setError] = React.useState(null);
const handleClick = async () => {
try {
// 위험한 비동기 로직
await doSomethingRisky();
} catch (e) {
setError(e);
}
};
if (error) throw error; // 경계로 전달
return <button onClick={handleClick}>실행</button>;
}6. 리셋 전략: stuck 상태에서 빠져나오기
- 버튼으로 상태 초기화: 위의 handleReset처럼 hasError를 false로
- key 변경으로 강제 재마운트: 라우트/파라미터 변화에 따라 ErrorBoundary에 key 부여
import { useLocation } from 'react-router-dom';
function RouteBoundary({ children }) {
const location = useLocation();
return (
<ErrorBoundary key={location.pathname}>
{children}
</ErrorBoundary>
);
}7. 모니터링 도구 연동(Sentry 예시)
import * as Sentry from '@sentry/react';
class ErrorBoundary extends React.Component {
// ...생략
componentDidCatch(error, info) {
Sentry.captureException(error, { extra: info });
}
// ...생략
}8. 라이브러리 사용: react-error-boundary
클래스를 직접 만들기 번거롭다면 검증된 라이브러리를 사용합니다. resetKeys로 자동 리셋, onReset로 정리 로직을 쉽게 붙일 수 있습니다.
import { ErrorBoundary } from 'react-error-boundary';
function Fallback({ error, resetErrorBoundary }) {
return (
<div role="alert">
<p>문제가 발생했습니다.</p>
<pre>{String(error)}</pre>
<button onClick={resetErrorBoundary}>다시 시도</button>
</div>
);
}
function Page({ userId }) {
return (
<ErrorBoundary
FallbackComponent={Fallback}
onError={(error, info) => console.log('log', error, info)}
onReset={() => {/* 캐시 초기화 등 */}}
resetKeys={[userId]} // userId 바뀌면 자동 리셋
>
<FetchUser id={userId} />
</ErrorBoundary>
);
}9. Suspense와 함께 쓰기
데이터 로딩은 Suspense, 오류는 Error Boundary가 담당하게 배치합니다.
<ErrorBoundary FallbackComponent={Fallback}>
<React.Suspense fallback={<div>로딩...</div>}>
<UserProfile />
</React.Suspense>
</ErrorBoundary>10. 한계와 체크리스트
- Error Boundary는 렌더/라이프사이클/하위 트리에서 발생한 오류만 잡습니다. 이벤트/비동기는 위 패턴으로 throw하여 승격하세요.
- SSR 오류는 서버에서 try/catch로 처리하고, 클라이언트 전환 후엔 Boundary를 사용하세요.
- 너무 큰 범위를 한 경계로 감싸면 작은 오류에도 큰 UI가 사라집니다. 기능 단위로 잘게 나누세요.
- 경계가 폴백 상태에 고착되지 않도록 리셋 경로를 반드시 제공합니다(버튼, resetKeys, key 변경).
- 로깅은 민감정보를 제외하고 수집하며, 사용자 행동 컨텍스트(페이지, 파라미터)를 함께 기록하면 원인 분석이 빨라집니다.
실무 팁: 라우트 경계 + 위젯 경계를 병행하고, 비동기 오류는 "state에 담고 render에서 throw" 패턴으로 일관되게 경계로 올리면 유지보수가 쉬워집니다.
'React' 카테고리의 다른 글
| React와 Redux Toolkit으로 상태 관리 구조화하기 (1) | 2026.04.17 |
|---|---|
| React Testing Library로 컴포넌트 단위 테스트 작성하기 (0) | 2026.04.17 |
| React 앱에서 Lazy Loading 이미지 처리하기 (1) | 2026.04.16 |
| React에서 Formik과 Yup으로 폼 검증 구현하기 (0) | 2026.04.15 |
| React Router v6의 최신 기능과 사용법 (0) | 2026.04.15 |