Recent Posts
Recent Comments
Link
Today
Total
«   2026/10   »
일 월 화 수 목 금 토
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
관리 메뉴

Jiseong's Web Dev blog

선언적으로 에러처리 아주 깔끔하게 하기 (Next.js 15) 본문

React

선언적으로 에러처리 아주 깔끔하게 하기 (Next.js 15)

jijiseong 2024. 10. 29. 21:27

🙃 Intro

class CustomError extends Error {  
  public code: string;  

  constructor({ message, code }: { message: string; code: string }) {  
    super(message);  
    this.code = code;  
  }  
}  

export default APIError;

error.tsx 는 왜 Error를 상속한 CustomError을 받을 수 없는걸까?

error.tsx 에다가 백엔드에서 정의한 code 값을 넘겨서, 그 값에 따라 정교하게 처리하고싶은데...

에러 처리는 위임하여 역할을 분리하는게 좋다했는데...

📜 error.tsx

먼저, 공식문서를 통해 error.tsx에 대해 정확히 알아보자.
error.tsx는 React Error Boundary를 사용하여 page 컴포넌트를 래핑한다.
자식 컴포넌트에서 throw 된 Error들을 받아 처리할 수 있다.

📜 props

error, reset 을 받는다.
공식문서를 살펴보면, 정확히 An instance of an Error object라고 명시되어있다.
Error를 상속한 객체를 throw 를 해도 그 속의 Error만 받는다.

📜 Error Handling

Next.js 15 버전으로 업데이트 되면서 expected errors and uncaught exceptions 두 가지로 구분한다고 더 강조하고있다.
(14버전 문서에서도 handle unexpected runtime errors 라는 문장이 있긴 하지만, 더욱 강조 되어있다.)
나는 이 때까지 error.js에서 예상한 에러, 예상치 못한 에러 모두를 처리해야하는 것으로 착각했다.
공식문서를 꼼꼼히 읽자 ㅠㅠ.

Next.js 15, React 19 버전 기준으로 아래 처럼 관리하라고한다.

  • expected error는 return value로 관리하라
    react 19 버전의 API useFormState는 error를 return value로 관리하도록 디자인되어있다. (try-catch 를 통한 throw Error는 지양!)
  • unexpected error는 throw하여 error.js 에서 처리하라
    error.jsx 또는 global-error.jsx 를 사용해 예상치 못한 에러를 처리한다.

에러를 return value로 관리하는 것, try-catch를 지양하는 것에대해 의문을 품었다.
React는 선언적 프로그래밍을 지향한다.
에러를 return value로 관리하면 선언형 패턴으로 관리할 수 있다.

반면 try-catch를 사용하면 명령형 패턴으로 관리하게된다.
React가 지향하는 바와 엇갈리게된다.
(개인적으로 try-catch의 들여쓰기가 너무 못생겼다고 생각한다.)

🧑🏻‍💻 examples

react 19 버전의 API useFormState를 사용할때 예제이다.

'use server'

import { redirect } from 'next/navigation'

export async function createUser(prevState: any, formData: FormData) {
  const res = await fetch('https://...')
  const json = await res.json()

  if (!res.ok) {
    return { message: 'Please enter a valid email', code: 101 }
  }

  redirect('/dashboard')
}

그러면 아래처럼 사용할 수 있다.

function Form() {
  const { data } = useFormStatus();
  data.message // will be updated to "Please enter a valid email" after running the action.
  return (
    <form action={createUser}>
      <Submit />
    </form>
  );
}

tanstack-qeury를 사용할때 예제이다.
React 19 버전이 아니더라도, tanstack-query 라이브러리를 사용한다면 아래처럼 처리할 수 있다.

async function createUser(prevState: any, formData: FormData) {
  const res = await fetch('https://...')
  const json = await res.json()

  const EXPECTED_ERROR_CODES = [101, 102, ...]
  if (EXPECTED_ERROR_CODES.includes(json.code)) {
    throw new CustomError({message: json.message, code: json.code})
  }
  if (!res.ok){
    throw new Error("Unexpected Error!: Can not create user.")
  }

  //...
}

function useCreateUser() {
  return(
    useMutation({
      mutationFn: createUser
      onError: (error) => { 
          if(!(error instanceof CustomError)) return;
        // 여기서 error.code 에 따라 예상가능한 에러처리
      }
      // unexpect error 라면 throw
      throwOnError: (error) => !(error instanceof CustomError)
    })
  )
}

예상치 못한 에러는 Error 객체로, 예상한 에러는 code를 포함해 CustomError 객체로 throw 하는 방식이다.

후기

필요한 API만 찾아 선택적으로 읽는 것이 습관이 들었다.
요즘 공식문서를 꼼꼼하게 읽어봐야겠다고 생각이 많이든다.