Frontend Error Architecture #1 - 경계와 책임

의도한 에러 vs 의도하지 않은 에러를 분류하고, 서버·네트워크·인터랙션·렌더링·브라우저 5개의 경계에 변환과 처리의 책임을 부여하는 전략

📄 [1편] 경계와 책임

TL;DR

  • Next.js는 에러를 '의도한(Expected)'과 '의도하지 않은(Unexpected)' 두 범주로 나눠요.
  • 에러를 처리할 5개의 경계(Boundary)를 정의하고, 각 경계에 변환 또는 처리의 책임을 부여해요.
  • Error Boundary는 렌더링 과정의 에러만 포착해요. 사용자 인터랙션이나 네트워크 통신에서 발생하는 에러에는 별도의 경계가 필요해요.

0. 왜 이런 고민을 하나요?

실제로 업무를 진행하며 Next.js 프로젝트를 만들다 보면, 기능 구현 일정의 압박에 따라 다음과 같은 상황이 발생해요.

  • 화면마다 제각각인 try ... catch, 임의의 문자열로 던지는 에러, 서로 다른 분기 로직.
  • 각 이벤트 핸들러에서 개별적으로 처리되는 Toast와 Alert.
  • Error Boundary만 믿다가 이벤트/비동기에서 터지는 예외 방치.
  • 서버가 주는 에러 Body가 제각각이라 클라이언트 조건문 폭발.

이 시리즈는 이러한 혼란에 질서를 부여하기 위한 최소 규칙을 정리하는 것에서 시작했어요.


1. 에러 분석: 적을 알아야 이긴다

에러 처리 전략을 세우기 전에 에러를 분류하는 것부터 시작해요.

에러를 바라보는 축은 여러 가지가 있어요. 발생 시점(컴파일 vs 런타임), 전파 방식(동기 vs 비동기), 발생 위치(클라이언트, 네트워크, 서버) 등이 있죠. 하지만 이 모든 축을 항상 고려하면서 코딩하기는 어려워요.

이 시리즈에서는 '의도'와 '위치'라는 두 가지 기준으로 실용적인 전략을 세울 거예요.

의도에 따른 분류가 가장 중요해요.

  • 의도한 에러(Expected): 비밀번호 불일치, 잔액 부족 등 개발자가 예상한 실패 흐름. 사용자에게 행동 교정을 요구할 수 있어요.
  • 의도하지 않은 에러(Unexpected): DB 연결 실패, 타입 에러 등 예측 불가능한 시스템 이슈. 사용자는 할 수 있는 게 없고, 시스템을 보호하고 개발자에게 알려야 해요.

이 분류가 이후 시리즈 전체의 뼈대가 됩니다.


2. 전략 수립: 경계를 정의하고, 책임을 부여하기

앞으로 설정할 모든 에러 처리 전략은 '경계(Boundary)'라는 개념 위에서 펼쳐져요.

1) 서버 경계 (Server Boundary)

클라이언트 요청이 도달하는 진입점이자, 응답을 보내는 전송점이에요. 내부 시스템 에러가 노출되지 않도록 방어하고, 실패를 표준화된 형식으로 변환하여 전달해요. Next.js의 Route Handler와 Server Action이 이 역할을 담당합니다.

2) 네트워크 경계 (Network Boundary)

클라이언트가 서버와 통신하는 경계에요. HTTP 상태 코드, 타임아웃, 네트워크 단절 같은 통신 에러를 일관된 형식으로 정규화해요. fetch 래퍼 함수가 이 역할을 합니다.

3) 인터랙션 경계 (Interaction Boundary)

사용자 인터랙션이 시작되는 최전선이에요. 폼 제출, 버튼 클릭 등에서 발생하는 에러를 포착해 즉각적이고 구체적인 UX 피드백을 제공해요.

4) 렌더링 경계 (Rendering Boundary)

데이터가 UI로 렌더링되는 과정에서 발생하는 에러를 국소적으로 격리해요. Next.js의 error.tsx와 React의 ErrorBoundary가 이 역할을 합니다.

💡 1% 디테일: 최상단 레이아웃을 위한 최후의 방어선 (global-error.tsx) error.tsx는 자신과 동일한 세그먼트에 있는 layout.tsx의 에러는 잡지 못해요. 최상단 루트 레이아웃에서 발생하는 치명적 에러를 막으려면 반드시 앱 루트에 global-error.tsx를 두어야 해요.

5) 브라우저 경계 (Browser Boundary)

위의 어떤 경계에서도 놓친 런타임 에러를 포착하는 최후의 수단이에요. window.onerrorwindow.onunhandledrejection이 이 역할을 합니다.

역할 부여: '변환'과 '처리'

  • 변환(Transforming): 원본 에러를 일관된 표준 형식으로 가공. 서버/네트워크 경계가 담당.
  • 처리(Handling): 변환된 에러를 바탕으로 UX 피드백 또는 로깅 수행. 인터랙션/렌더링/브라우저 경계가 담당.

3. 결론

우리는 복잡한 에러 환경에 5개의 명확한 방어선을 세웠어요. 다음 편에서는 모든 에러를 담을 단일 에러 모델과 도구들을 구현합니다.