Jiseong's Web Dev blog
선언적으로 에러처리 아주 깔끔하게 하기 (Next.js 15) 본문
🙃 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 버전의 APIuseFormState는 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만 찾아 선택적으로 읽는 것이 습관이 들었다.
요즘 공식문서를 꼼꼼하게 읽어봐야겠다고 생각이 많이든다.
'React' 카테고리의 다른 글
| RadixUI의 asChild, BaseUI의 render (0) | 2026.02.07 |
|---|---|
| useEffect 남용을 줄이자. (0) | 2025.03.02 |
| 웹뷰에서 소셜 로그인시 문제 해결하기 (2) | 2024.09.15 |
| optimistic update를 사용하면서 고민했던 상황 (0) | 2024.06.09 |
| Dropdown, 합성 컴포넌트 패턴으로 구현하기 (1) | 2024.06.03 |