junyeokk
Blog
React·2026. 01. 07

Error Boundary

React 컴포넌트에서 에러가 발생하면 어떻게 될까? 기본적으로는 전체 컴포넌트 트리가 언마운트되면서 빈 화면이 보인다. 사용자 입장에서는 앱이 죽은 것이다.

Error Boundary는 이 문제를 해결한다. 자식 컴포넌트 트리에서 발생한 JavaScript 에러를 잡아서, 전체 앱을 날리는 대신 대체 UI(fallback)를 보여준다.


왜 try-catch로 안 되는가

일반적인 JavaScript 에러 처리는 try-catch다. 그런데 React에서는 이게 잘 안 먹는다.

jsx
// 이건 렌더링 중 발생하는 에러를 못 잡는다
try {
  return <BrokenComponent />;
} catch (e) {
  return <p>에러!</p>;
}

왜냐하면 JSX에서 <BrokenComponent />는 즉시 실행되는 게 아니라, React 엘리먼트 객체를 생성하는 것이기 때문이다. 실제 렌더링은 React가 나중에 하고, 그때 발생하는 에러는 이 try-catch 스코프 밖이다.

Error Boundary는 React의 렌더링 파이프라인 안에서 동작하기 때문에 렌더링 중 에러를 잡을 수 있다.


동작 원리

Error Boundary는 두 가지 생명주기 메서드를 사용하는 클래스 컴포넌트다.

jsx
class ErrorBoundary extends React.Component {
  state = { hasError: false };

  // 에러 발생 시 state 업데이트 → fallback 렌더링
  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  // 에러 로깅 등 부수 효과 처리
  componentDidCatch(error, errorInfo) {
    console.error('Caught by boundary:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return <h1>문제가 발생했습니다.</h1>;
    }
    return this.props.children;
  }
}

getDerivedStateFromError: 정적 메서드다. 에러가 발생하면 호출되고, 반환값으로 state를 업데이트한다. 렌더 단계에서 실행되므로 부수 효과(side effect)를 넣으면 안 된다.

componentDidCatch: 커밋 단계에서 실행된다. 에러 로깅 서비스에 보고하거나 추가 정보를 수집하는 데 쓴다. errorInfo.componentStack에 어떤 컴포넌트에서 에러가 터졌는지 스택 정보가 담겨 있다.


잡을 수 있는 것 vs 없는 것

Error Boundary가 잡는 에러:

  • 자식 컴포넌트의 렌더링 중 에러
  • 생명주기 메서드 안에서의 에러
  • 생성자(constructor) 안에서의 에러

잡지 못하는 에러:

  • 이벤트 핸들러 — 렌더링 밖에서 실행되므로 일반 try-catch로 처리
  • 비동기 코드 — setTimeout, Promise 등
  • 서버 사이드 렌더링 — 서버에는 DOM 라이프사이클이 없음
  • Error Boundary 자기 자신의 에러

이벤트 핸들러 에러를 못 잡는 건 의도적이다. 이벤트 핸들러에서 에러가 나도 React는 무엇을 렌더링해야 하는지 이미 알고 있으므로, 화면이 깨지지 않는다. 렌더링 중 에러와는 상황이 다르다.


배치 전략: 어디에 두느냐가 중요하다

Error Boundary의 위치가 에러 발생 시 사용자 경험을 결정한다.

최상위에 하나만: 앱 전체가 fallback으로 대체된다. "뭔가 잘못됐습니다" 페이지. 최후의 방어선으로는 좋지만, 작은 에러에도 앱 전체가 날아간다.

기능 단위로 감싸기: 특정 섹션만 fallback을 보여주고, 나머지는 정상 동작. 예를 들어 채팅 위젯에서 에러가 나도 메인 콘텐츠는 살아있다.

jsx
<Layout>
  <ErrorBoundary fallback={<p>피드를 불러올 수 없습니다.</p>}>
    <Feed />
  </ErrorBoundary>
  <ErrorBoundary fallback={<p>채팅 오류</p>}>
    <Chat />
  </ErrorBoundary>
</Layout>

실전에서는 두 전략을 함께 쓴다. 최상위 boundary로 전체를 감싸고, 중요한 기능 단위마다 추가 boundary를 둔다.


함수 컴포넌트에서 쓰기: react-error-boundary

Error Boundary는 클래스 컴포넌트로만 만들 수 있다. getDerivedStateFromError에 대응하는 Hook이 아직 없기 때문이다. 그래서 react-error-boundary 같은 라이브러리가 나왔다.

jsx
import { ErrorBoundary } from 'react-error-boundary';

function ErrorFallback({ error, resetErrorBoundary }) {
  return (
    <div>
      <p>오류: {error.message}</p>
      <button onClick={resetErrorBoundary}>다시 시도</button>
    </div>
  );
}

<ErrorBoundary
  FallbackComponent={ErrorFallback}
  onReset={() => {
    // 상태 초기화 등
  }}
>
  <App />
</ErrorBoundary>

핵심 이점은 에러 복구다. 기본 Error Boundary는 한번 에러가 발생하면 fallback 상태에 갇히는데, resetErrorBoundary로 정상 상태로 돌아갈 수 있다. resetKeys prop을 주면 특정 값이 변할 때 자동으로 리셋된다.

jsx
<ErrorBoundary
  FallbackComponent={ErrorFallback}
  resetKeys={[userId]}  // userId가 바뀌면 자동 리셋
>
  <UserProfile userId={userId} />
</ErrorBoundary>

핵심 정리

Error Boundary는 React의 선언적 에러 처리 방식이다. 명령적으로 에러를 잡아서 분기하는 게 아니라, "이 영역에서 에러가 나면 이걸 보여줘"라고 선언한다. React가 컴포넌트 트리를 관리하듯, 에러도 트리 구조에 맞게 관리하는 것이다.


관련 문서