React 프로젝트에서 API 에러 처리, ApiErrorBoundary로 정리해봤다
Error boundary를 사용한 에러잡기
#react-query #ErrorBoundary #zustand
React 프로젝트에서 react-query와 zustand를 같이 쓰고 있다. zustand는 전역 상태 관리용으로, react-query는 서버에서 데이터 불러오는 용도로 쓴다. 기능적으로는 잘 동작하지만, 어느 순간부터 API 호출이 실패했을 때 처리하는 코드가 너무 반복적 이라는 걸 느꼈다. "데이터를 불러오지 못했습니다", "다시 시도" 같은 UI를 페이지마다 복붙하다 보니 유지보수도 귀찮아지고, 에러 UI가 페이지마다 들쑥날쑥해서 사용자 입장에서도 불편했다. 그래서 react-error-boundary 를 기반으로 ApiErrorBoundary 라는 컴포넌트를 만들었고, 지금은 꽤 만족스럽게 쓰고 있다. 이 글에서는 도입 전/후 차이와 코드 구조를 정리해본다. 에러 처리 안 했을 때의 상황 react-query에서 제공하는 isError, error, refetch 같은 값을 활용해서 API 에러를 처리했었다. 그 당시 코드는 이런 형태였다. 처음엔 문제 없어 보였지만, 페이지가 늘어날수록 이 코드가 여기저기 중복되기 시작했다. 게다가 error.message를 직접 찍는 건 사용자에게 보여주기엔 친절하지도 않고, 모든 페이지에서 일관된 에러 UI를 만들기도 힘들었다. ApiErrorBoundary 도입 그래서 react-error-boundary 라이브러리를 활용해 ApiErrorBoundary라는 컴포넌트를 만들었다. 핵심 개념은 다음과 같다. react-query에 useErrorBoundary: true 옵션을 주면 쿼리 실패 시 ErrorBoundary로 에러가 전달됨 ApiErrorBoundary에서 그걸 받아 fallback UI로 넘겨서 처리함 ApiErrorBoundary 컴포넌트 fallback UI 구성 이렇게 만들어두면 컴포넌트 단에서 에러 처리는 깔끔하게 이 fallback에서 끝난다. 이제는 쿼리 컴포넌트는 이렇게 간단하게 만든다. 그리고 내부 쿼리에서는 useErrorBoundary: true 옵션만 설정해주면 된다. 쿼리 안에서는 이제 isError나 refetch() 같은 걸 따로 처리할 필요가 없다. 로딩 중 UI만 간단히 보여주면 끝이다. GlobalErrorBoundary ApiErrorBoundary는 컴포넌트 단에서 API 에러만 처리한다. 네트워크 연결 끊김처럼 전역적인 문제 는 따로 GlobalErrorBoundary 로 처리하고 있다. 구조는 이렇게 생겼다 fallback 예시 ApiErrorFallback 안에서 Network Error가 발생하면 throw 해서 여기서 처리하게 했다. 도입하고 나서 달라진 점 이 구조로 바꾼 이후로, 쿼리 관련 UI 코드는 거의 로딩만 처리하게 되었고 에러 핸들링은 fallback 하나로 관리할 수 있게 됐다. 사용자 경험도 훨씬 자연스럽고 안정적으로 느껴진다. 요약하면 API 요청 실패 는 ApiErrorBoundary에서 처리한다. 네트워크 끊김 / 앱 전체 에러 는 GlobalErrorBoundary에서 처리한다. react-query에서 useErrorBoundary: true를 꼭 써야 에러가 바운더리로 전달된다. 인증 실패 시 zustand로 사용자 상태를 초기화해 로그아웃 처리한다. 마무리 React 프로젝트가 커지면 커질수록 에러 처리는 구조적으로 정리해두는 게 중요하다. 개별 페이지마다 일일이 에러 처리하는 방식은 한계가 있다. ApiErrorBoundary와 GlobalErrorBoundary를 함께 쓰면, 에러가 발생했을 때 사용자에게 더 일관된 메시지를 줄 수 있고, 개발자 입장에서도 훨씬 관리가 편하다. 개인적으로는 이 구조를 도입한 이후로, 에러 관련 고민이 확 줄었다. react-query와 zustand를 같이 쓰고 있다면 한 번쯤 적용해볼 만한 방식이다.