Skip to main content

error.js

에러 파일(error.js)은 페이지 컴포넌트의 에러를 처리하기 위해 사용하는 에러 컴포넌트입니다. 에러 컴포넌트를 사용하지 않으면 페이지에서 에러가 발생했을 때 페이지 접속이 안되는 큰 문제가 발생하므로 꼭 알고 있어야 합니다.

에러 컴포넌트 위치

에러 컴포넌트는 먼저 app 폴더 밑에 하나 생성해 두어야 합니다.

app/
page.js
error.js

위 에러 컴포넌트는 page.js 파일(페이지 컴포넌트)에서 에러가 발생했을 때 표시됩니다.

에러 컴포넌트 기본

에러 컴포넌트는 클라이언트 컴포넌트로 선언해야 합니다. 다음과 같이 말이죠.

app/error.js
'use client'

export default function Error() {
return <div>에러가 발생했어요</div>
}

그리고 에러 컴포넌트는 기본적으로 다음 2개의 프롭스를 받습니다.

app/error.js
'use client'
import { useEffect } from 'react'

export default function Error({ error, reset }) {
useEffect(() => {
console.error(error)
}, [error])

return (
<div>
<p>에러가 발생했어요</p>
<button onClick={reset}>다시 시도하기</button>
</div>
)
}

여기서 error 프롭스는 에러 정보가 담긴 객체를 의미합니다. reset 프롭스는 해당 URL로 다시 한번 요청을 보내면서 새로 페이지를 그리게 합니다. 마치 새로고침과 같은 효과죠.

에러 컴포넌트 배치 전략

에러 컴포넌트는 전역에 하나만 두고 쓰는 것보다 각 페이지별로 에러 UI를 표시할 수 있게 각 페이지 컴포넌트 영역에 배치하는 것이 좋습니다. 예를 들어, 다음과 같이 2개의 페이지 컴포넌트가 존재한다고 해봅시다.

app/
login/
page.js ← '/login'으로 접근했을 때 표시될 페이지 컴포넌트
profile/
page.js ← '/profile'로 접근했을 때 표시될 페이지 컴포넌트

로그인 페이지와 프로필 페이지에 각각 에러가 발생했을 때 각기 다른 에러 UI를 표시하고 싶다면 어덯게 해야 할까요? 다음과 같이 페이지 컴포넌트 옆에 error.js 파일을 배치합니다.

app/
login/
page.js
error.js ← 로그인 페이지에 에러가 발생했을 때 표시될 에러 UI
profile/
page.js
error.js ← 프로필 페이지에 에러가 발생했을 때 표시될 에러 UI

이렇게 각 페이지의 에러 UI를 다르게 정의하고 싶을 때 에러 컴포넌트를 페이지 컴포넌트 옆에 배치하면 됩니다.

에러 컴포넌트의 영향 범위

앞에서 살펴본 것처럼 에러 컴포넌트는 전역으로 1개만 놓고 쓸 수도 있지만, 각 페이지 별로 에러 케이스를 대응하기 위해 페이지 옆에 에러 컴포넌트를 배치하는게 일반적입니다. 그럼 이번엔 반대로 아래와 같은 구조에서 layout.js 파일에 에러가 발생하면 어떻게 될까요?

app/
login/
layout.js
page.js
error.js

위 폴더 구조에서는 로그인의 레이아웃 컴포넌트에서 에러가 발생하더라도 에러 컴포넌트가 표시되지 않습니다. 그 이유는 넥스트에서 내부적으로 다음과 같은 컴포넌트 위계를 만들어 두었기 때문이죠.

next-component-hierachy

만약 이 로그인 레이아웃에서 발생한 에러를 처리하고 싶다면 다음과 같이 한 단계 위에 에러 파일을 생성하면 됩니다.

app/
error.js ← 로그인 레이아웃에서 발생한 에러를 처리할 수 있음
login/
layout.js
page.js
error.js

이처럼 에러 컴포넌트를 생성할 때는 페이지 컴포넌트의 에러를 처리할지 레이아웃 컴포넌트의 에러를 처리할지를 고민하고 위치를 결정해야 합니다.

tip

레이아웃 컴포넌트 뿐만 아니라 템플릿 컴포넌트(template.js)의 에러도 같은 레벨의 에러 컴포넌트로 처리할 수 없습니다. 레이아웃 컴포넌트 에러 처리와 동일하게 한 단계 더 위에 에러 파일을 배치해 주세요.

에러 바운더리

에러 바운더리는 에러 처리를 위한 리액트 컴포넌트입니다. 넥스트에서는 에러 파일(error.js)을 생성하면 내부적으로 에러 바운더리가 제공됩니다. 에러 컴포넌트의 영향 범위에서 봤던 그림에서 볼 수 있듯이 말이죠. 에러 바운더리는 컴포넌트 내부에서 발생하는 자바스크립트 에러를 처리해 줍니다. 특정 컴포넌트에 등록된 하위, 손자 컴포넌트들의 에러까지 모두 처리할 수 있습니다.

역할

에러 바운더리는 보통 다음의 역할을 수행합니다.

  • 하위 컴포넌트 트리의 에러가 발생했을 때 폴백(fallback) UI를 표시한다.
  • 에러 정보를 서버에 로깅한다.

제어 불가능한 에러

에러 바운더리가 컴포넌트 내의 모든 에러를 탐지할 수 있는 건 아닙니다. 아래 4가지 유형의 에러는 잡을 수 없습니다.

  • 이벤트 핸들러 내에서 발생한 에러
  • setTimeout, requestAnimationFrame 등의 비동기 코드로 발생된 에러
  • 서버 사이드 렌더링 시점의 에러
  • 에러 바운더리 컴포넌트 자체에서 발생한 에러

이벤트 핸들러에서 발생되는 에러는 렌더링 시점에 발생되지 않기 때문에 에러 바운더리로 처리하지 않습니다. 에러 바운더리는 주로 컴포넌트를 렌더링/리렌더링 하면서 발생되는 에러를 처리하죠.

기본 코드

에러 바운더리는 리액트 16부터 등장한 클래스 컴포넌트입니다. 현재 시점까지도 클래스 컴포넌트로 사용되고 있습니다. 코드를 볼까요?

class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}

static getDerivedStateFromError(error) {
// 에러가 발생되면 이 메서드가 실행되고 `hasError` 상태를 업데이트합니다.
// `hasError` 상태가 `true`로 바뀌면서 폴백 UI가 그려집니다.
return { hasError: true };
}

componentDidCatch(error, errorInfo) {
// 이 메서드에서 주로 서버로 에러 정보를 보냅니다.
logErrorToMyService(error, errorInfo);
}

render() {
// 폴백 UI는 요런식으로 그리세요.
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}

return this.props.children;
}
}

이 코드는 요렇게 사용합니다.

<ErrorBoundary>
<UserProfile />
</ErrorBoundary>

루트 레이아웃의 에러 처리 방법

이런 규칙으로 에러 파일을 추가하더라도 루트 레벨의 레이아웃이나 템플릿 파일의 에러는 제어할 수 없을텐데요. 그럴 때는 앱 폴더의 루트 레벨에 app/global-error.js을 만들어서 모든 에러를 처리할 수 있게 만들 수 있습니다. 다만, 이 전역 에러 처리 파일은 특정 영역에 대한 에러 처리나 UI 표시를 담당하지 않고 전역 레벨의 에러 페이지 역할을 하므로 <html>, <body> 등의 태그를 선언해 주어야 합니다.