본문 바로가기

study/Next.js

Next.js - App router Error Handling(오류 처리)

error.js 파일 컨벤션은 중첩 라우트에서 발생하는 예상치 못한 런타임 에러를 우아하게 처리할 수 있도록 도와줍니다.

  • 자동으로 라우트 세그먼트와 자식 세그먼트를 리액트 에러 바운더리로 감싸줍니다.
  • 세분성을 조절하기 위해 파일 시스템 계층 구조를 사용하여 특정 세그먼트에 최적화 된 에러 UI를 생성할 수 있습니다.
  • 나머지 기능은 그대로 유지한 채로 에러가 발생한 세그먼트만 분리할 수 있습니다.
  • 전체 페이지 로딩을 하지 않고 에러를 핸들링 할 수 있는 기능을 추가할 수 있습니다.

error.js 파일을 라우트 세그먼트에 위치하고 리액트 컴포넌트를 export 하면 에러 UI를 생성할 수 있습니다.

 

 

공식 문서에서 제공하는 error.js의 기본 예시 코드는 아래와 같습니다.

'use client' // 에러 컴포넌트는 클아이언트 컴포넌트여야 한다.
 
import { useEffect } from 'react'
 
export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  useEffect(() => {
    // 에러 로그
    console.error(error)
  }, [error])
 
  return (
    <div>
      <h2>Something went wrong!</h2>
      <button
        onClick={
          // 세그먼트를 다시 리렌더링 하여 에러를 핸들링함
          () => reset()
        }
      >
        Try again
      </button>
    </div>
  )
}

 

 

error.js 동작 방식

error.js 동작 방식

 

  • error.js 파일은 page.js 컴포넌트나 중첩 된 자식 세그먼트를 감싸는 리액트 에러 바운더리를 자동으로 생성합니다.
  • error.js 파일에서 export 되는 리액트 컴포넌트는 리액트 에러 바운더리에 fallback 컴포넌트로 사용 됩니다.
  • 에러 바운더리 내에서 에러가 발생하면 에러 객체가 포함 된 fallback 컴포넌트는 렌더링 됩니다.
  • fallback 에러 컴포넌트가 활성화 됬을 때 에러 바운더리 컴포넌트의 상위 컴포넌트로 자리 잡은 레이아웃은 스테이트를 유지하고 인터랙션 능력을 유지합니다. 그리고 에러 컴포넌트는 에러를 핸들링 할 수 있는 기능을 UI로 보여줄 수 있습니다.

 

오류 복구하기

에러의 발생 원인은 때로는 일시적일 수 있습니다.
이런 경우엔 다시 시도해보는게 이슈를 해결할 방법이 될 수 있습니다.

 

에러 컴포넌트는 reset() 함수를 사용하여 유저가 에러를 핸들링 할 수 있도록 처리합니다.
함수가 실행 되면 에러 바운더리 내의 컨텐츠를 리렌더링 합니다.
만약 성공했다면 fallback 에러 컴포넌트는 성공한 렌더링 결과물과 스왑하게 됩니다.

 

'use client'
 
export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <div>
      <h2>Something went wrong!</h2>
      // reset 함수를 통해 리렌더링 가능
      <button onClick={() => reset()}>Try again</button>
    </div>
  )
}

 

 

중접 라우팅의 경우

특수 파일로 만들어진 리액트 컴포넌트는 특별한 중첩 계층 구조를 가진채 렌더링 됩니다.

예를 들어 layout.js 파일과 error.js 파일을 가지고 있는 중첩 된 두 라우트 세그먼트는 다음의 간단한 컴포넌트 계층 구조를 가지게 됩니다.

 

중첩 컴포넌트 계층 구조는 중첩 라우트 간 다음의 error.js 파일의 행동을 내포하고 있습니다.

  • 에러는 가장 가까운 부모 에러 바운더리로 버블링 됩니다. 따라서 error.js 파일은 중첩 되어 있는 자식 세그먼트의 에러도 핸들링 할 수 있습니다. 에러 UI의 많고 적은 입자감은 중첩 폴더 혹은 라우트에서 error.js 를 어느 위치에 놓는지로 성취할 수 있습니다.
  • error.js 경계는 동일 세그먼트에 선언 된 layout.js 에서 발생 되는 에러를 핸들링 하지 못합니다. 왜냐면 에러 바운더리는 레이아웃 컴포넌트 내부에 위치하기 때문입니다.

 

layout.js 오류 처리

error.js 경계는 동일 세그먼트의 layout.js, template.js 컴포넌트의 에러를 잡아내지 못합니다.
이런 의도 된 계층 구조 원칙은 네비게이션 같은 형제 라우트간 공유 되는 중요 UI를 에러가 발생하더라도 볼 수 있도록 유지합니다.

 

특정 레이아웃, 템플릿에서 에러를 핸들링하고 싶다면 레이아웃의 부모 세그먼트에 error.js 파일을 생성하면 됩니다.

 

루트 레이아웃, 템플릿에서 에러를 핸들링하기 위해선 error.js 의 변형인 global-error.js 컴포넌트를 사용하면 됩니다.

 

 

Root layout.js 오류 처리

app/error.js 루트 바운더리는 app/layout.js , app/template.js 같은 루트 컴포넌트의 에러를 잡아내지 못합니다.

 

특별히 루트 컴포넌트에서 에러를 핸들링 하기 위해 error.js의 변형인 app/global-error.js 파일을 app 디렉토리에 위치하여 해결할 수 있습니다.

 

루트 error.js 컴포넌트와 다르게 global-error.js 에러 바운더리는 어플리케이션 전체를 감싸고 fallback UI가 활성화 되면 루트 레이아웃을 대체하게 됩니다.
이런 이유 때문에 global-error.js 컴포넌트는 자신만의 <html> , <body> 태그를 반드시 포함해야 합니다.

 

global-error.js 컴포넌트는 최소한의 파편(granular) 에러 UI이고 어플리케이션 전체에서 "catch-all" 에러 핸들링 컴포넌트라고 간주할 수 있습니다.
내부의 다른 error.js 바운더리가 대부분의 에러를 핸들링 하기도 하고, 루트 컴포넌트는 대부분 다이나믹하지 않기 때문에 에러 바운더리가 트리거 될 일은 흔하지 않습니다.

 

global-error.js 컴포넌트가 정의 되었다고 해도 글로벌하게 공유 되는 UI와 브랜딩을 가지고 있는 루트 레이아웃 내부에서 fallback 컴포넌트가 렌더링 되는 루트 error.js 컴포넌트를 정의하는 것을 추천합니다.(global-error.js 컴포넌트는 fallback UI가 렌더링 되면 전체 레이아웃도 같이 사라지기 때문입니다.)

 

// app/global-error.tsx
'use client'
 
export default function GlobalError({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <html>
      <body>
        <h2>Something went wrong!</h2>
        <button onClick={() => reset()}>Try again</button>
      </body>
    </html>
  )
}