Error Boundary를 이용하여 똑똑하게 에러처리해보기
# React Error Boundary를 이용한 에러처리 경험을 소개합니다.
2024년 05월 29일
도입 배경
❗ 현재 Offispace 프로젝트에서는 서버 측 에러 페이지 404.tsx와 공통 서버 에러 페이지 _error.tsx로 구분되어 관리되고 있다. 그러나 클라이언트 측 에러는 별도로 관리되고 있지 않아, 다음과 같은 문제가 발생한다.
발생 가능한 문제 시나리오
글 상세페이지에서는 총 2개의 API가 호출된다.
- 글 상세 정보를 조회하는 post, id
- 해당 글에 달린 댓글 정보를 가져오는 AllComments, id
현재는 API가 제대로 동작하여 글 상세 페이지가 잘 보여지지만, 어느 한 곳이라도 에러가 발생한다면?
MSW를 이용하여 글 상세 데이터를 가져오는 API에 임의로 에러를 발생시켜보자.
❗ 댓글은 정상적으로 가져오고 있지만, 글 상세 데이터를 가져오는 중 에러가 발생해 화면이 아예 터져버렸다. 따로 에러 처리를 하지 않아, 빈 화면이 출력되고 있는데, 유저 입장에서는 로딩중인지, 에러가 발생한건지, 화면이 넘어가긴 한건지 알 수 없어, 매우 큰 불편함이 생길것이다.
React Error Boundary 도입
React에서 제공하는 Error Boundary 라는 개념을 이용하여 위에서 발생한 문제를 내용을 해결할 수
있다. Error Boundary는 렌더링 중 hasError 상태를 추적하여 하위 트리 에서 발생하는 에러를 잡아내어
처리 할 수 있도록 도와준다.
(https://nextjs.org/docs/pages/building-your-application/configuring/error-handling)
Error Boundary는 위와 같이 작성된다. 다만 Next에서 제공되는 코드는 type이 적용되지 않아 Error Boundary Type관련 글을 찾아보고 적용시켜줬다.
https://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/error_boundaries/
이제 코드를 뜯어보자.
constructor
: 컴포넌트의 초기 상태를 설정하고, hasError
를 false
로 초기화한다.
해당 메서드는 컴포넌트에서 에러가 발생했을 때 호출되고, hasError
를 true
로 설정하여 상태를 업데이트하는 역할을 담당한다.
위 메서드는 실제로 에러가 발생했을 때 호출되며, 에러 정보와 함께 추가적인 에러 정보를 콘솔에 로그로 출력하는 역할을 담당한다. 주로 에러 추적 서비스에 에러 정보를 보낼 때 사용된다.
- 해당 메서드는 컴포넌트의 UI를 렌더링하는데, 만약
hasError
가true
라면, 에러가 발생했음을 나타내는 UI “문제가 발생했어요.”를 렌더링한다.fallbackComponent
가 제공된 경우 해당 컴포넌트를 렌더링하고, 그렇지 않으면 기본 메시지("문제가 발생했어요.")를 보여준다. - 에러가 발생하지 않았을 경우에는 자식 컴포넌트들을 그대로 렌더링한다.
동작 과정 요약
- 초기화:
constructor
를 통해hasError
상태를false
로 설정 - 에러 발생: 자식 컴포넌트에서 에러가 발생하면
getDerivedStateFromError
가 호출되어hasError
상태를true
로 설정. - 에러 처리:
componentDidCatch
가 호출되어 에러와 에러 정보를 콘솔에 출력. - 렌더링:
render
메서드는hasError
상태를 확인하여,true
인 경우fallbackComponent
나 기본 에러 메시지를 렌더링하고,false
인 경우 자식 컴포넌트들을 그대로 렌더링한다.
공식문서에서 제공하는 사용법은 매우 간단하다. 위 코드를 사용하여 ErrorBoundary.tsx 컴포넌트를 만들고, 사용처에서 해당 컴포넌트를 감싸주면 된다. 단순히 ErrorBoundary를 감싸주는것만으로도 전체 클라이언트 에러를 감지 할 수 있게 되는것이다.
결과
ErrorBoundary
로 앱을 감싸주고 확인해보면, 이전과 달리 빈 화면이 아닌, "에러 발생"이라는 문구가 표시되면서 사용자에게 에러가 발생했음을 확실히 인지 시킬 수 있다.
하지만, 해당 방식은 댓글은 정상적으로 가져오고 있지만, 여전히 글 상세 데이터에서 에러가 발생했다고 해서 전체 페이지를 사용할 수 없게 되는 문제가 발생한다. 이는 매우 좋지 않은 사용자 경험이라고 판단하여, 에러의 범위를 좁히는 방식으로 해결하고자 했다. 글 상세 API에서 에러가 발생하면 해당 부분만 에러 처리하고, 댓글은 정상적으로 작동하는것이다.
따라서 위 코드처럼 ErrorBoundary
를 에러 발생 컴포넌트인 PostDetail
를 감싸서 export default
해주었다. Error Boundary는 렌더링 중 hasError 상태를 추적하여 하위 트리 에서 발생하는 에러를 잡아내어 처리 할 수 있다라고 소개했는데, 이 동작을 이용해 하단에서 발생한 에러가 상위 트리까지 올라가는 과정을 차단하고, 미리 에러를 처리할 수 있도록 한것이다.
이제 다시 에러를 발생시켜보면, 전과 달리 댓글을 가져오는 API는 정상적으로 작동하여 화면에 잘 출력되고, 에러가 발생한 글 상세 API 부분만 에러 처리가 된 것을 볼 수 있다. 이를 통해 사용자는 앱이 완전히 멈춘 것이 아니라 특정 부분에서 오류가 발생했음을 인지하게 되어 안심 할 수 있게 된다.
해당 ErrorBoundary
컴포넌트는 댓글을 가져오거나, 글 상세 데이터를 가져오는 중 에러가 발생할 때 분기적으로 오류 컴포넌트의 디자인을 다르게 적용할 수 있다. 즉, 특정 API에 대해서 별도의 fallback 에러 컴포넌트를 사용할 수 있는 것이다.
따라서 기존 코드에 fallbackComponent?: React.ReactNode
를 추가시켜 Props로 컴포넌트를 받을 수 있도록 설정하고, 이를 return {this.props.fallbackComponent}
코드를 통해 fallbackComponent
가 들어온다면 해당 컴포넌트를 렌더링 시킬 수 있도록 수정했다.
이제 위 화면처럼 글을 가져올 때 에러가 발생하면
fallbackComponent
로 넘겨준 에러 컴포넌트가 화면에 표시된다.
해당 작업을 진행하면서, 그렇다면 각 컴포넌트별로 API를 구분하여 사용처 전부 errorboundary로 묶어주고 각 API마다 fallbackComponent
로 어디서 어떤 에러가 발생했는지 사용자에게 정확하게 인지시켜줄 수 있지 않을까? 라는 생각을 가지게 됐지만, 이는 현실적으로 어려운 일 일것이다.
따라서 위 코드처럼, 인증 에러, 네트워크 에러 등 공통적으로 일어날 수 있는 케이스로 에러를 최대한 분류하고, 그에 따라 선언적으로 에러를 처리하면, 사용자는 상황에 따라 좀 더 다듬어진 에러를 파악 할 수 있을것이다.