RSS듀오랩스
Next.js

Next.js 오류 처리 파일: error.tsx, global-error, not-found

작성자
듀오랩스 대표·10분 읽기

Next.js 앱 라우터에는 오류를 다루는 파일이 셋 있습니다. error.tsx, global-error.tsx, not-found.tsx 입니다. 이름만 보면 「오류 화면, 전체 오류 화면, 404 화면」으로 나뉘는 것 같은데, 실제로는 각자 감싸는 범위와 잡는 대상이 다릅니다. 하나로 다 될 것 같아서 error.tsx 만 두면, 정작 화면이 하얗게 비는 상황에서 그 파일이 뜨지 않습니다.

예상한 오류와 예상하지 못한 오류

오류 처리 문서는 오류를 두 갈래로 나누는 것에서 시작합니다. 예상된 오류(expected errors)와 잡히지 않은 예외(uncaught exceptions)입니다.

예상된 오류는 서버 쪽 폼 검증 실패나 요청 실패처럼 정상 운영 중에 일어나는 오류입니다. 문서는 이런 오류에 try/catch 로 던지지 말고 반환값으로 모델링하라고 권합니다. 서버 함수라면 { message: "..." } 같은 값을 돌려주고, 화면은 useActionState 로 그 값을 받아 보여 줍니다.

"use server";
export async function createPost(prevState, formData) {
  const res = await fetch(/* ... */);
  if (!res.ok) return { message: "저장하지 못했습니다" };
}

오류 파일들은 나머지 갈래, 즉 예상하지 못한 예외를 위한 장치입니다. 폼 검증 실패를 error.tsx 로 처리하려 하면 화면 전체가 오류 화면으로 바뀌어 사용자가 입력하던 내용을 잃습니다.

error.tsx 가 감싸는 범위

error.tsx 는 그 세그먼트에 React Error Boundary 를 두는 파일입니다. 범위는 error 문서에 정확히 적혀 있습니다.

In the component hierarchy, error.js wraps loading.js, not-found.js, page.js, and nested layout.js files in a React error boundary. It does not wrap the layout.js or template.js above it in the same segment.

같은 폴더의 레이아웃은 감싸지 않습니다. 레이아웃에서 데이터를 읽다 실패하면 그 세그먼트의 error.tsx 가 아니라 부모 세그먼트의 경계가 잡습니다. 레이아웃에 조회를 넣어 두고 같은 폴더에 error.tsx 를 뒀다면, 기대한 화면이 뜨지 않는 조합입니다.

경계 컴포넌트는 두 가지를 props 로 받습니다. error 와 재시도 함수입니다. Next.js 16 에서 재시도 함수의 이름은 unstable_retry 이고, 문서는 이 함수가 경계 안의 자식들을 다시 가져와 다시 렌더링한다고 설명합니다. 성공하면 오류 화면이 원래 내용으로 바뀝니다.

"use client";
export default function Error({ error, unstable_retry }) {
  return (
    <div>
      <p>문제가 발생했습니다.</p>
      <button onClick={() => unstable_retry()}>다시 시도</button>
    </div>
  );
}

운영에서 오류 메시지가 사라지는 이유

error 로 받은 객체를 그대로 화면에 찍어 두면 개발 중에는 잘 보이다가 운영에서 비어 보입니다. 문서의 설명입니다.

During development, the Error object forwarded to the client will be serialized and include the message of the original error for easier debugging. However, this behavior is different in production to avoid leaking potentially sensitive details.

운영에서는 민감한 내용이 새지 않도록 메시지를 내려보내지 않습니다. 대신 error.digest 가 있습니다. 서버 로그의 같은 오류에 붙는 값이라, 화면의 digest 를 로그에서 찾아 실제 오류를 확인하는 용도입니다.

이 성질은 반대로 읽을 수도 있습니다. 화면의 오류 카드에 digest 가 없다면 서버가 만든 오류가 아니라 브라우저에서 난 오류입니다. 이 블로그의 React error 300 기록에서 digest 가 없다는 점이 서버 로그를 뒤지지 않아도 됐다는 단서였습니다.

루트 레이아웃이 깨지면 global-error

error.tsx 가 같은 세그먼트의 레이아웃을 감싸지 않는다면, 루트 레이아웃에서 난 오류는 누가 잡을까요. 그 자리가 global-error.tsx 입니다.

문서는 이 파일이 활성화되면 루트 레이아웃이나 템플릿을 대체한다고 적습니다. 그래서 이 파일은 자기 <html><body> 태그를 직접 정의해야 하고, 필요한 전역 스타일과 폰트도 스스로 불러와야 합니다. 루트 레이아웃이 깨진 상황에서 뜨는 화면이니 당연한 조건입니다.

한 가지 제약도 적혀 있습니다. 오류 경계는 클라이언트 컴포넌트여야 하므로 metadatagenerateMetadata 를 내보낼 수 없습니다. 제목이 필요하면 React 의 <title> 컴포넌트를 쓰라고 안내합니다.

없는 페이지는 오류가 아니다

not-found.tsx 는 예외를 잡는 파일이 아니라, 코드가 notFound() 를 부를 때 보여 줄 화면입니다. 상품이 없거나 글이 삭제된 경우처럼 예상된 상황입니다.

Next.js 16 에는 파일이 하나 더 있습니다. not-found 문서는 둘을 이렇게 구분합니다.

  • not-found.js: 라우트 세그먼트에서 notFound() 를 부를 때
  • global-not-found.js: 어떤 경로에도 걸리지 않는 요청 전체를 위한 404 페이지. 라우팅 단계에서 처리되므로 레이아웃이나 페이지를 렌더링하지 않아도 됩니다

주소 자체가 존재하지 않는 경우와, 주소는 있지만 대상이 없는 경우를 다른 파일이 맡는 셈입니다.

notFound() 를 부를 때 주의할 점이 하나 더 있습니다. 같은 세그먼트나 상위 세그먼트에 loading.tsx 가 있으면 로딩 화면이 먼저 스트리밍되기 시작해 상태 코드가 200 으로 고정됩니다. 화면은 「찾을 수 없음」인데 응답은 200 인 상태가 됩니다. 이 블로그에서 실제로 그랬고, 해당 경로의 loading.tsx 를 빼서 404 를 되찾았습니다.

파일별로 정리하면

파일 맡는 것 놓치기 쉬운 점
반환값 + useActionState 폼 검증 실패 같은 예상된 오류 오류 파일로 처리하면 입력 내용을 잃는다
error.tsx 그 세그먼트 아래 렌더링 예외 같은 폴더의 layout.tsx 는 감싸지 않는다
global-error.tsx 루트 레이아웃과 템플릿의 오류 <html>, <body> 를 직접 정의해야 한다
not-found.tsx notFound() 호출 상위 loading.tsx 가 있으면 상태 코드가 200 이 된다
global-not-found.js 어떤 경로에도 없는 주소 라우팅 단계에서 처리된다

문서는 라우트 세그먼트에 묶이지 않는 컴포넌트 단위의 오류 복구를 위해 unstable_catchError 함수도 안내합니다.

제가 새 프로젝트에서 두는 순서는 이렇습니다. 먼저 global-error.tsx 를 두어 최악의 경우 흰 화면 대신 안내가 나오게 하고, 독립적으로 실패해도 되는 영역(대시보드 위젯, 사이드바)에 error.tsx 를 둡니다. 그리고 notFound() 를 부르는 경로에는 그 위에 loading.tsx 가 있는지 확인합니다.

여기까지가 확실한 부분

예상된 오류를 반환값으로 다루라는 권고, error.js 가 같은 세그먼트의 레이아웃을 감싸지 않는다는 점, 운영에서 오류 메시지가 전달되지 않고 digest 가 남는다는 점, global-error 가 루트 레이아웃을 대체하며 자체 <html><body> 가 필요하다는 점, not-foundglobal-not-found 의 구분은 Next.js 16 문서 기준입니다. 재시도 함수 이름이 unstable_retry 인 것에서 보이듯 이 API 는 아직 이름이 확정되지 않았습니다. 상위 loading.tsx 때문에 404 가 200 이 된 사례는 2026년 9월 18일 이 블로그에서 확인하고 고친 내용입니다.

마지막 수정:

공유하실 때는 출처(Duolabs)와 원문 주소를 표시해 주세요.