FE/NextJS
NextJS 공식문서 파헤치기 (Error Handling)
-Daniel-
2024. 12. 10. 22:43
0️⃣ INTRO
- 에러는 예상된 에러와 예상치 못한 예외 두가지로 나눌 수 있다.
- 예상된 에러를 반환 값으로 모델링: 서버 액션에서 예상된 에러를 try/catch 문 보다는 useActionState를 사용하여 에러를 관리하고 클라이언트에 반환하는것이 좋다.
- 예상치 못한 에러는 에러 경계로 처리: error.tsx, global-error.tsx 파일을 사용해 에러 경계를 구현하고 에러를 처리한다.
❓ useActionState ❓
- React v19 에 정식 도입된 훅으로서 폼 작업의 결과에 따라 상태를 업데이트할 수 있다.
- 기존의 폼 액션 함수와 초기 state를 전달받고, 폼에서 사용할 새로운 액션을 반환한다.
- 최신 폼 state와 액션이 여전히 진행(Pending) 중인지 여부도 반환한다.
import { useActionState } from "react";
// 함수가 실행될때 첫 번째 인수로 폼의 이전 state를 전달한다.
async function increment(previousState, formData) {
return previousState + 1;
}
function StatefulForm({}) {
const [state, formAction] = useActionState(increment, 0);
return (
<form>
{state}
<button formAction={formAction}>Increment</button>
</form>
)
}
1️⃣ Handling Expected Errors
- 예상된 에러는 서버 측 폼 검증이나 실패한 요청등 정상적인 애플리케이션 운영 중에 발생할 수 있는 에러이다.
- 이러한 에러는 명시적으로 처리하고 클라이언트에 반환해야 한다.
서버 액션을 통한 예상된 에러 핸들링
useActionState
훅(이전에는useFormState
)을 사용하여 서버 액션의 상태를 관리하고 에러를 처리한다.try/catch
블록을 피하고, 에러를 throw 하는 대신 반환 값으로 모델링해야 한다.- 아래 예제는 서버 액션을 정의한 후 useActionState 훅을 통해 반환된 state를 사용하여 에러를 컨트롤 하는 예제이다.
// app/actions.js
'use server'
import { redirect } from 'next/navigation'
export async function createUser(prevState, formData) {
const res = await fetch('https://...')
const json = await res.json()
if (!res.ok) {
return { message: 'Please enter a valid email' }
}
redirect('/dashboard')
}
// app/ui/signup.jsx
'use client'
import { useActionState } from 'react'
import { createUser } from '@/app/actions'
const initialState = {
message: '',
}
export function Signup() {
const [state, formAction] = useActionState(createUser, initialState)
return (
<form action={formAction}>
<label htmlFor="email">Email</label>
<input type="text" id="email" name="email" required />
{/* ... */}
// 클라이언트 컴포넌트에서 반환된 상태를 사용하여 토스트 메시지 표시
<p aria-live="polite">{state?.message}</p>
<button>Sign up</button>
</form>
)
}
서버 컴포넌트에서 예상되는 에러 핸들링
- 서버 컴포넌트 내에서 데이터를 가져올 때는 응답을 사용하여 조건부로 에러 메시지를 렌더링하거나
redirect
할 수 있다.
export default async function Page() {
const res = await fetch(`https://...`)
const data = await res.json()
if (!res.ok) {
return 'There was an error.'
}
return '...'
}
2️⃣ Uncaught Exceptions
- 정상적인 애플리케이션 흐름 중에 발생해서는 안되는 버그나 문제를 나타내는 예기치 않은 에러
- 이러한 에러는 throw 하여 에러 경계에서 처리해야 한다.
🚨 상황에 따른 에러처리 🚨
- 일반적인 경우: 루트 레이아웃 하위에서 발생한 예기치 않은 에러는 `error.js`로 처리한다.
- 선택적인 경우: 중첩된 `error.js` 파일(ex: `app/dashboard/error.js`)을 사용하여 세분화된 예기치 않은 에러를 처리한다.
- 드문 경우: 루트 레이아웃에서 발생한 예기치 않은 에러는 `global-error.js`로 처리한다.
Error Boundaries 사용
- Nextjs는 예상치 못한 예외를 처리하기 위해
Error Boundaries
를 사용한다. - 자식 컴포넌트에서 발생한 에러를 캐치하고 충돌한 컴포넌트 트리 대신 fallback UI를 표시한다
- 경로 세그먼트 내에
error.jsx
|error.tsx
파일을 추가하고 React 컴포넌트를 내보내어 에러 경계를 생성할 수 있다. - 에러 바운더리는 클라이언트 컴포넌트로 선언되어야 한다.
'use client' // Error boundaries must be Client Components
import { useEffect } from 'react'
export default function Error({ error, reset }) {
useEffect(() => {
// Log the error to an error reporting service
console.error(error)
}, [error])
return (
<div>
<h2>Something went wrong!</h2>
<button
onClick={
// Attempt to recover by trying to re-render the segment
() => reset()
}
>
Try again
</button>
</div>
)
}
중첩된 라우트에서 에러 핸들링
- 에러는 가장 가까운 부모 에러 경계로 전파된다.
- 경로 계층 구조의 다양한 수준에서
error.jsx
|error.tsx
파일을 배치하여 세분화된 에러 처리가 가능하다.
전역 에러 핸들링
- 루트 레이아웃에서 에러를 처리해야 할 때는 app/global-error.js 를 사용해야 한다.
- 루트 앱 디렉토리에 위치해야한다.
- 전역 에러 UI는 활성화될 때 루트 레이아웃이나 템플릿을 대체하므로
<html>
,<body>
태그를 정의해야 한다.
'use client' // Error boundaries must be Client Components
export default function GlobalError({ error, reset }) {
return (
// global-error must include html and body tags
<html>
<body>
<h2>Something went wrong!</h2>
<button onClick={() => reset()}>Try again</button>
</body>
</html>
)
}