에러도 UX다 – React에서의 체계적인 에러 처리 전략

date
May 19, 2025
slug
error-as-ux-react-error-handling
author
status
Public
tags
React
summary
type
Post
thumbnail
category
updatedAt
May 21, 2025 02:45 PM
 
"사용자에게 어떤 에러 경험을 제공하고 있나요?”
 

1. 들어가며

웹 서비스가 점점 복잡해지고 기능이 늘어날수록, 다양한 이유로 예기치 않은 에러가 발생할 수 있습니다.
서버 API 호출이 실패하거나, 인증 토큰이 만료되거나, 클라이언트 코드에서 예외가 발생할 수도 있죠.
이럴 때 사용자에게 아무런 안내 없이 화면이 멈춰버린다면, 서비스 신뢰도와 사용 경험에 적지 않은 영향을 줄 수 있습니다. 반면, 에러를 예측하고 적절하게 처리하는 구조를 갖추면, 사용자에게 안정적인 인상을 줄 수 있고, 개발자 입장에서도 유지보수와 디버깅이 쉬워집니다.
이 글에서는 리액트 환경(React + Vite)에서 구현한 에러 처리 시스템의 설계 구조와 적용한 과정을 공유하려고 합니다.
 

2. 에러를 분류하자

먼저, 다양한 에러 상황을 발생 원인과 처리 방식에 따라 분류했습니다.
에러 유형
예시
처리 방식
서버 에러
API 요청 실패 (500, 404 등)
사용자 메시지 + UI
클라이언트 에러
JS 런타임 에러
ErrorBoundary 처리
인증 에러
토큰 만료, 401 등
자동 로그아웃 + 로그인 리디렉션
커스텀 에러
특정 UX 흐름 필요
컴포넌트에서 개별 처리
네트워크 에러
인터넷 끊김 등
Retry 유도 or 토스트 알림
 

3. 에러 처리의 전체 흐름

에러는 Axios에서 시작해 공통 포맷으로 변환되고, 처리 로직을 거쳐 UI까지 이어집니다.
 

4. Axios에서 시작하는 에러 표준화

Axios는 다양한 에러 형태를 던집니다

  • 서버 응답 에러 (error.response 있음)
  • 네트워크 에러 (response 없음)
  • 인증 실패 (401 → 토큰 갱신 로직 필요)
이 모든 경우를 createServerError(code, message)정형화된 객체로 감싸기 때문에(표준화하기), 이후 로직이 간단해집니다.
 
 
interceptor 내부의 에러 처리 흐름
  1. Axios 응답 에러 → status code 확인
  1. 백엔드에서 제공한 code 우선 사용
  1. 없으면 HTTP status → 에러 코드 매핑 테이블로 변환
  1. 에러 메시지까지 합쳐서 createServerError로 감싸기
 

📌 createServerError

이 함수는 일반 Error 객체에 code 속성을 추가함으로써,
  • 타입 가드 (isServerError)를 사용 가능하게 하고,
  • 모든 에러를 동일한 구조로 다룰 수 있게 만들어줍니다. 즉, error.code, error.message를 기반으로 안정적인 UI 분기 처리가 가능합니다.
 

5. 에러를 공통 구조로 감싸기

이 구조를 기반으로 에러 메시지, UI 전략, 로그 수집, 리다이렉트 경로까지 일관되게 처리합니다.
 

6. 서버 에러 코드와 분류

이 구조는 각 에러가 어떻게 처리되어야 하는지를 명확히 해줍니다.
예를 들어 USER_NOT_FOUND는 단순 알림, DUPLICATE_NICKNAME은 페이지 이동이 필요한 커스텀 처리로 분리됩니다.
 

7. 에러 분석 및 분류

  • axios 에러인지, 클라이언트 에러인지 구분
  • 정의된 에러 코드에 따라 사용자 메시지/처리 전략 결정
 
 

8. 처리 실행 함수 (handleError)

 

9. react-query와 연동

⚠️ throwOnError: true로 설정해야 ErrorBoundary에 위임됩니다.
 

10. 에러 UI 처리 전략 요약 (예시)

분류
처리 방식
예시 메시지
ALERT
toast.error()
"사용자를 찾을 수 없습니다."
AUTH
toast + router.replace('/login')
"세션이 만료되었습니다."
ERROR_BOUNDARY
<ErrorBoundary> + Fallback UI
"예기치 못한 오류가 발생했어요"
CUSTOM
useMutation({ onError }) 컴포넌트 처리
"닉네임이 중복되었습니다."
 

11. ErrorBoundary와 통합

react-error-boundary의 resetErrorBoundary()는 에러 상태를 초기화하여 다시 렌더링을 유도합니다.
QueryErrorResetBoundary와 함께 쓰면, react-query 캐시까지 초기화해 API 재시도를 할 수 있습니다. ⇒ 이를 통해 사용자에게 “다시 시도하기” 흐름을 제공할 수 있습니다.
 

12. 커스텀 에러: 컴포넌트에서 직접 처리

커스텀 에러는 컴포넌트 내부 UX 흐름과 밀접하게 연관되어 있기 때문에 전역 처리보다 useMutation().onError에서 직접 처리하는 것이 명확하고 유연합니다.
 
isCustomError 예시 코드
 

13. Sentry 로깅 전략

  • ✅ 수집
    • 사용자 조작 없이 해결할 수 없는 오류
    • 디버깅을 위한 API 실패 (e.g. 서버 500, 네트워크 장애)
  • ⛔ 제외
    • 사용자가 해결할 수 있는 예상 가능한 오류 (e.g. 로그인 실패)
    • UX 상 이미 안내된 알림성 에러 (toast로 처리되는 404 등)
    •  
💡 Tip: 모든 에러를 Sentry에 보내면 ‘노이즈’가 많아지고,
중요한 이슈를 놓치기 쉬워집니다.
 
따라서 다음과 같은 기준을 명확히 세워두는 것이 좋습니다.
  • 단순 UX 알림(toast.error)으로 충분한 오류는 로깅하지 않고 무시
  • 예상 외의 예외 상황(서버 오류, 알 수 없는 에러 등)은 반드시 Sentry로 전송
  • 필요하다면 handleError 내부에서 조건 분기로 로깅 여부를 판단
이렇게 하면 불필요한 로깅은 줄이고, 중요한 오류만 집중적으로 모니터링할 수 있습니다.
 

14. 서버 에러 코드 추가 가이드

  1. 백엔드와 에러 코드 및 의미 정의 협의
  1. 백엔드에서 에러 코드 적용 후 프론트에 코드 추가
 

15. 결론

에러는 언제든 발생할 수 있습니다. 하지만 어떻게 설계하고 대응하느냐에 따라, 사용자 경험은 완전히 달라집니다.
사용자가 문제를 만났을 때 보게 되는 UI는 단순한 화면이 아니라, 우리가 제품을 얼마나 책임감 있게 만들고 있는지를 보여주는 부분이라는 생각이 듭니다.
 
이 글에서 다룬 것처럼,
  • 에러를 표준화된 구조로 감싸고
  • 카테고리 기반으로 처리 전략을 나누며
  • UI와 연결되는 흐름까지 일관되게 설계하면
 
“에러 처리”는 더 이상 귀찮은 예외가 아니라, 제품의 품질을 지탱하는 강력한 기반이 될 수 있습니다.
 
“예기치 못한 상황을 '예상 가능한 흐름'으로 바꾸는 것이 제품을 개발하는 사람들에게 주어진 중요한 과제이자 책임이 아닐까요?”
 
💡 이 글에서 소개한 방식은 하나의 예시일 뿐입니다.
여러분의 서비스에 맞게 조정하며 활용해보시길 바랍니다!