Next.js Error Boundary 완벽 가이드: 런타임 오류를 우아하게 처리하는 5가지 핵심 기법

운영팀 단체 채팅이 난리가 났습니다. “홈페이지가 안 열려요! 화면이 온통 하얘요!”
모니터링 플랫폼을 열어 보니 서드파티 컴포넌트 하나가 고장 나면서 페이지 전체를 함께 무너뜨렸습니다. 사용자가 보는 것은 새하얀 화면뿐이고, “오류가 발생했습니다”라는 안내조차 없습니다. 프로덕션에서 멀쩡히 돌아가던 페이지가 데이터 형식 하나가 잘못되거나 API 하나가 시간 초과되는 바람에 애플리케이션 전체가 갑자기 중단된 것입니다. 기존의 try-catch는 React 컴포넌트 렌더링 계층까지 처리하지 못합니다. 결국 사용자는 흰 화면을 멍하니 바라보다가 조용히 페이지를 닫습니다.
사용자 경험 연구에 따르면 페이지의 흰 화면은 80%가 넘는 사용자의 즉각적인 이탈을 초래합니다.
다행히 Next.js는 이런 런타임 오류를 우아하게 처리할 수 있는 Error Boundary 메커니즘을 제공합니다. 흰 화면을 막는 데 그치지 않고 친절한 폴백 화면을 보여 주며, 사용자가 직접 복구할 수 있도록 “재시도” 버튼까지 제공할 수 있습니다. 이 글에서는 기본적인 error.tsx부터 전역 안전망인 global-error.tsx, Server Components의 특별한 처리 방식까지 Next.js Error Boundary의 전체 사용법을 살펴봅니다.
이 글을 다 읽고 나면 애플리케이션이 오류 상황에서도 더 품위 있게 동작하도록 만들고, 한밤중에 호출을 받아 버그를 고치는 참사를 피하는 방법을 알게 될 것입니다.
Error Boundary가 필요한 이유: 기존 오류 처리의 한계
처음 React를 사용할 때는 try-catch로 모든 오류를 해결할 수 있다고 생각했습니다. 하지만 얼마 지나지 않아 현실은 그렇지 않다는 것을 알게 됐습니다.
try-catch의 세 가지 치명적인 한계
첫째, 동기 코드의 오류만 포착할 수 있습니다. try 블록 안에 JSON.parse(badData)를 작성하면 오류를 잡을 수 있습니다. 하지만 컴포넌트 렌더링 도중 오류가 발생하면 어떨까요? 안타깝지만 잡지 못합니다.
둘째는 더 까다롭습니다. 이벤트 핸들러 안에서 발생하는 비동기 오류입니다. 예를 들어 클릭 이벤트에서 API를 호출했는데 API에 장애가 발생하면 try-catch로도 처리할 수 없습니다. 비동기 코드가 실행되는 시점에는 try-catch의 컨텍스트가 이미 끝났기 때문입니다.
셋째이자 가장 치명적인 문제는 React 컴포넌트 렌더링 오류입니다. 컴포넌트의 return 문에서 undefined 값의 속성에 접근하면 페이지가 곧바로 흰 화면이 됩니다. 이 상황에서는 try-catch가 아무런 역할도 하지 못합니다.
React Error Boundary의 작동 원리
React는 일찍부터 이 문제를 인식하고 Error Boundary 메커니즘을 도입했습니다. 원리는 꽤 간단합니다. 컴포넌트 트리는 러시아 인형처럼 겹겹이 중첩되어 있고, 오류는 내부에서 바깥쪽으로 한 단계씩 “버블링”하다가 가장 가까운 Error Boundary 컴포넌트를 만나면 멈춥니다.
전통적인 방식은 클래스 컴포넌트를 작성하고 componentDidCatch와 getDerivedStateFromError라는 두 생명주기 메서드를 구현하는 것입니다. 솔직히 매번 클래스 컴포넌트를 작성하는 일은 번거롭습니다. 요즘은 함수형 컴포넌트에 익숙한 개발자도 많은데, 이 두 메서드는 함수형 컴포넌트에서 아예 사용할 수 없습니다.
Next.js의 우아한 해결책
Next.js 13에서 App Router가 도입된 뒤 Error Boundary가 한 단계 더 추상화되어 아주 간단해졌습니다. 라우트 디렉터리에 error.tsx 파일만 만들면 해당 라우트의 오류 경계로 자동 설정됩니다. 클래스 컴포넌트를 작성하거나 상태를 직접 관리할 필요 없이 Next.js가 모두 처리합니다.
또 하나 중요한 점은 Next.js의 Error Boundary가 서버와 클라이언트의 오류를 모두 처리할 수 있다는 것입니다. Server Components가 서버 렌더링 도중 오류를 일으켜도 가장 가까운 error.tsx가 이를 포착합니다. 기존 React에서는 불가능했던 일입니다.
유일하게 주의할 점은 error.tsx 파일 자체가 클라이언트 컴포넌트여야 하므로 맨 앞에 'use client' 표시를 추가해야 한다는 것입니다. 오류 상태와 복구 로직을 처리하는 React hooks가 필요하고, hooks는 클라이언트에서만 실행할 수 있기 때문입니다.
Facebook Messenger가 대표적인 사례입니다. 사이드바, 대화창, 메시지 입력 영역을 각각 Error Boundary로 감쌌습니다. 한 영역이 중단되어도 다른 영역은 그대로 작동합니다. 사용자는 문제가 있었다는 사실조차 알아차리지 못할 수 있습니다.
이것이 Error Boundary의 핵심 가치입니다. 국소 오류가 전역 재난으로 번지지 않게 합니다.
error.tsx 사용법 - 국소 오류 경계
이제 실전으로 들어가 보겠습니다. error.tsx는 어떻게 작성할까요?
기본 구조: 5분 만에 시작하기
원하는 라우트 디렉터리에 error.tsx를 만들고 다음 코드를 붙여 넣습니다.
'use client'
import { useEffect } from 'react'
export default function Error({
error,
reset
}: {
error: Error & { digest?: string }
reset: () => void
}) {
useEffect(() => {
// Sentry 같은 모니터링 플랫폼으로 오류 전송
console.error('포착한 오류:', error)
}, [error])
return (
<div className="flex flex-col items-center justify-center min-h-screen p-4">
<h2 className="text-2xl font-bold mb-4">문제가 발생했습니다</h2>
<p className="text-gray-600 mb-4">
{error.message || '페이지를 불러오지 못했습니다'}
</p>
<button
onClick={() => reset()}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
다시 시도
</button>
</div>
)
}
핵심은 다음과 같습니다.
- ‘use client’를 빠뜨리면 안 됩니다: 파일 맨 앞에 이 줄이 반드시 있어야 하며, 없으면 Next.js에서 오류가 발생합니다.
- error 객체: 오류 정보와 스택을 포함합니다. Next.js 15에서 오류 추적에 사용하는
digest필드도 새로 추가됐습니다. - reset 함수: 클릭하면 오류 경계 안의 콘텐츠를 다시 렌더링해 사용자에게 스스로 복구할 기회를 줍니다.
오류 버블링 메커니즘: 엘리베이터처럼 한 층씩 위로
처음에는 이 메커니즘이 다소 헷갈릴 수 있습니다. 다음 디렉터리 구조를 보면 바로 이해할 수 있습니다.
app/
├── layout.tsx # 루트 레이아웃
├── error.tsx # 루트 라우트 아래의 오류 포착 (A)
├── page.tsx # 홈페이지
├── dashboard/
│ ├── layout.tsx # dashboard 레이아웃
│ ├── error.tsx # dashboard 아래의 오류 포착 (B)
│ └── page.tsx # dashboard 페이지
└── profile/
└── page.tsx # profile 페이지
dashboard/page.tsx를 렌더링할 때 오류가 발생했다고 가정해 봅시다. 어느 파일이 포착할까요? 정답은 가장 가까운 상위 error.tsx인 (B)입니다.
그렇다면 profile/page.tsx에서 오류가 발생하면 어떨까요? profile 디렉터리에는 error.tsx가 없으므로 오류가 계속 위로 버블링되어 (A)가 포착합니다.
주의해야 할 함정이 하나 있습니다. error.tsx는 같은 수준에 있는 layout.tsx의 오류를 포착하지 못합니다. 오류 경계 자체가 layout 안에 포함되기 때문에 layout이 중단되면 오류 경계도 아직 로드되지 않은 상태이기 때문입니다. dashboard/layout.tsx의 오류를 포착하려면 app/error.tsx에서 처리해야 합니다.
reset()의 올바른 사용법
reset 함수는 아주 신기하게 들리지만, 실제로는 오류 컴포넌트의 서브트리를 다시 렌더링하는 기능입니다. 다음과 같은 일시적 오류에 적합합니다.
- API 요청 시간 초과(재시도하면 성공할 수 있음)
- 네트워크 불안정으로 인한 리소스 로드 실패
- 사용자 입력이 유발한 경계 조건
하지만 undefined.property에 접근하는 코드 버그라면 아무리 재시도해도 소용없습니다. 이런 경우에는 모니터링 플랫폼에서 오류를 확인한 뒤 신속히 코드를 수정해 배포해야 합니다.
일부 팀은 reset 로직에 카운터를 추가합니다. 재시도가 3회를 넘으면 “재시도” 버튼을 더 이상 표시하지 않고 페이지 새로고침이나 고객센터 문의를 안내합니다. 꽤 실용적인 방법입니다.
'use client'
import { useEffect, useState } from 'react'
export default function Error({ error, reset }: {
error: Error & { digest?: string }
reset: () => void
}) {
const [retryCount, setRetryCount] = useState(0)
const handleReset = () => {
setRetryCount(prev => prev + 1)
reset()
}
return (
<div>
<h2>오류가 발생했습니다</h2>
{retryCount < 3 ? (
<button onClick={handleReset}>
재시도 ({retryCount}/3)
</button>
) : (
<p>여러 번 재시도했지만 실패했습니다. 페이지를 새로고침하거나 <a href="/contact">문의해 주세요</a>.</p>
)}
</div>
)
}
global-error.tsx - 전역 오류를 위한 최후의 안전망
error.tsx는 강력하지만 한 가지 빈틈이 있습니다. 루트 레이아웃인 app/layout.tsx의 오류는 포착하지 못합니다. 이때 global-error.tsx가 필요합니다.
global-error.tsx는 언제 사용하나요?
솔직히 이 파일이 프로덕션 환경에서 실행되는 경우는 드뭅니다. 주로 다음 두 가지 재난급 상황을 처리합니다.
- 루트 layout.tsx 초기화 실패(예: 전역 상태 관리 라이브러리 장애)
- 모든 error.tsx가 포착하지 못한 오류
저는 이를 마지막 안전망이라고 생각합니다. 절대 사용할 일이 없기를 바라지만 반드시 마련해 두어야 합니다.
global-error.tsx의 특수성
일반 error.tsx와 비교했을 때 global-error.tsx에는 중요한 차이가 있습니다. <html>과 <body> 태그를 포함한 완전한 HTML 구조가 필요합니다.
루트 layout.tsx를 완전히 대체하기 때문입니다. 루트 레이아웃이 중단되면 페이지 프레임워크 전체가 사라지므로 global-error.tsx가 처음부터 최소한의 사용 가능한 페이지를 만들어야 합니다.
전체 코드는 다음과 같습니다.
'use client'
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
<html>
<body>
<div style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
minHeight: '100vh',
padding: '20px',
fontFamily: 'system-ui, sans-serif'
}}>
<h1>애플리케이션에 심각한 문제가 발생했습니다</h1>
<p style={{ color: '#666', marginBottom: '20px' }}>
{process.env.NODE_ENV === 'development'
? error.message
: '문제를 처리하고 있습니다. 잠시 후 다시 시도해 주세요.'}
</p>
<button
onClick={() => reset()}
style={{
padding: '10px 20px',
background: '#0070f3',
color: 'white',
border: 'none',
borderRadius: '5px',
cursor: 'pointer'
}}
>
애플리케이션 다시 불러오기
</button>
</div>
</body>
</html>
)
}
여기서는 Tailwind나 CSS 모듈 대신 인라인 스타일을 사용했습니다. 이유는 간단합니다. 이 시점에는 스타일 시스템조차 로드되지 않았을 수 있으므로 가장 기본적인 방식으로 페이지가 보이도록 보장해야 합니다.
개발 환경과 프로덕션 환경
주목할 만한 세부 사항이 있습니다. global-error.tsx는 프로덕션 환경에서만 작동합니다. 개발 환경에서는 디버깅할 수 있도록 Next.js가 계속 빨간색 오류 스택 페이지를 표시합니다.
프로덕션 환경에서는 기술적인 오류 정보를 숨기고 사용자에게 친절한 안내만 보여 주는 것이 좋습니다. 위 코드의 process.env.NODE_ENV 조건문이 바로 이 역할을 합니다. 사용자는 “TypeError: Cannot read property ‘map’ of undefined”에 관심이 없습니다. 그들이 알고 싶은 것은 “사용할 수 있는가”와 “언제 고쳐지는가”입니다.
global-error.tsx를 추가해야 할까요?
제 대답은 추가하라는 것입니다. 실행될 확률은 낮지만, 한 번 실행되는 상황은 큰 사고입니다. 이 안전망을 마련해 두면 최소한 브라우저 기본 화면인 “사이트에 연결할 수 없음” 대신 제대로 된 오류 페이지를 사용자에게 보여 줄 수 있습니다.
보험과 같습니다. 사고가 나기를 바라지는 않지만, 실제로 사고가 났을 때는 대비책이 없는 것보다 있는 편이 훨씬 낫습니다.
Server Components 오류 처리 시 특별히 고려할 점
Next.js 13+의 Server Components는 오류 처리에 새로운 과제를 가져왔습니다. 서버 오류와 클라이언트 오류는 처리 방식이 조금 다릅니다.
Server Components 오류는 어디로 갈까요?
처음 Server Components를 접했을 때는 저도 꽤 혼란스러웠습니다. 서버 컴포넌트는 서버에서 렌더링되는데, 여기서 오류가 발생하면 클라이언트의 error.tsx가 포착할 수 있을까요?
정답은 가능합니다. Next.js는 서버의 오류 정보를 클라이언트에 전달하고 가장 가까운 error.tsx를 실행합니다. 다만 중요한 보안 메커니즘이 있습니다. 프로덕션 환경에서는 민감한 서버 정보의 유출을 막기 위해 오류 정보가 정제됩니다.
예를 들어 데이터베이스 연결이 실패했을 때 개발 환경에는 전체 오류 스택이 표시되지만, 프로덕션 사용자는 “불러오지 못했습니다” 같은 일반적인 안내만 보게 됩니다.
예상된 오류와 예기치 않은 오류
이 구분은 매우 중요하며 공식 문서에서도 특별히 강조합니다. 다음 두 가지 오류를 구별해야 합니다.
예상된 오류: 비즈니스 로직 범위 안의 오류로, 명시적으로 처리해야 합니다.
- 폼 검증 실패(사용자 입력 형식이 잘못됨)
- API의 404 응답(데이터가 존재하지 않음)
- 권한 부족(사용자가 로그인하지 않음)
예기치 않은 오류: 코드 버그 또는 시스템 수준의 예외로, Error Boundary에 맡겨야 합니다.
- 데이터베이스 연결 실패
- 서드파티 서비스 장애
- 코드가 undefined 값의 속성에 접근함
예상된 오류는 Server Action 또는 데이터 가져오기 함수에서 try-catch로 처리한 뒤 오류 정보를 컴포넌트에 반환해야 합니다.
// app/actions.ts
'use server'
export async function createUser(formData: FormData) {
const email = formData.get('email') as string
// 예상된 오류: 이메일 형식이 잘못됨
if (!email.includes('@')) {
return { error: '유효한 이메일 주소를 입력해 주세요' }
}
try {
await db.user.create({ email })
return { success: true }
} catch (error) {
// 예기치 않은 오류: 데이터베이스 장애, Error Boundary에서 처리하도록 throw
throw new Error('사용자를 생성하지 못했습니다')
}
}
예기치 않은 오류는 그대로 throw하여 가장 가까운 error.tsx까지 버블링되도록 합니다.
데이터 가져오기에서의 오류 처리
Server Components에서 데이터를 가져올 때 저는 보통 다음처럼 처리합니다.
// app/posts/page.tsx
async function getPosts() {
const res = await fetch('https://api.example.com/posts')
// 예상된 오류: API가 오류 상태 코드를 반환함
if (!res.ok) {
// 오류 유형에 따라 명시적으로 처리할지 throw할지 결정
if (res.status === 404) {
return { posts: [], error: '데이터가 없습니다' }
}
// 서버 오류: Error Boundary에서 처리하도록 throw
throw new Error('데이터를 가져오지 못했습니다')
}
return { posts: await res.json() }
}
export default async function PostsPage() {
const { posts, error } = await getPosts()
// 오류 상태를 명시적으로 렌더링
if (error) {
return <div>게시물이 없습니다</div>
}
return (
<ul>
{posts.map(post => <li key={post.id}>{post.title}</li>)}
</ul>
)
}
이렇게 하면 사용자 경험이 더 좋아집니다. “데이터가 없습니다”라는 상태는 오류 페이지를 실행할 필요가 없고, 실제 시스템 오류가 발생했을 때만 error.tsx의 폴백 UI를 보여 줍니다.
error.digest의 유용한 활용법
Next.js 15에서는 error 객체에 자동으로 생성되는 고유 식별자인 digest 필드가 추가됐습니다.
어디에 쓸 수 있을까요? 사용자가 오류 페이지를 캡처해 고객센터에 보내며 “페이지가 열리지 않아요”라고 말하는 상황을 생각해 봅시다. 상담원이 이 digest로 로그를 조회하면 어떤 요청에서 언제 어떤 오류가 발생했는지 정확히 찾을 수 있습니다.
error.tsx에서 다음처럼 사용할 수 있습니다.
'use client'
export default function Error({ error }: { error: Error & { digest?: string }}) {
return (
<div>
<h2>오류가 발생했습니다</h2>
<p>오류 번호: {error.digest}</p>
<p>고객센터에 문의할 때 위 번호를 알려 주세요</p>
</div>
)
}
Sentry나 다른 모니터링 플랫폼과 함께 사용하면 digest를 통해 오류 추적 효율을 몇 배나 높일 수 있습니다.
프로덕션 환경 모범 사례
앞에서는 사용법을 살펴봤으니, 이제 제대로 사용하는 방법을 알아보겠습니다. 제가 직접 여러 문제를 겪은 뒤 정리한 경험입니다.
1. 세분화된 오류 경계 설계
루트 디렉터리에 error.tsx 하나만 두고 끝내면 안 됩니다. 핵심 기능 영역에는 별도의 오류 경계를 설정하는 것이 좋습니다.
예를 들어 전자상거래 사이트는 다음처럼 나눌 수 있습니다.
app/
├── error.tsx # 최종 안전망
├── (shop)/
│ ├── products/
│ │ └── error.tsx # 상품 목록 오류가 다른 영역에 영향을 주지 않음
│ ├── cart/
│ │ └── error.tsx # 장바구니 오류가 상품 탐색에 영향을 주지 않음
│ └── checkout/
│ └── error.tsx # 가장 중요한 결제 흐름을 별도로 처리
이렇게 하면 장바구니 컴포넌트가 중단되어도 사용자는 계속 상품을 둘러볼 수 있습니다. 사이트 전체를 사용할 수 없는 상황으로 번지지 않습니다.
2. 오류 모니터링과 리포팅
error.tsx의 useEffect는 오류를 전송하기에 완벽한 시점입니다.
'use client'
import { useEffect } from 'react'
import * as Sentry from '@sentry/nextjs'
export default function Error({ error, reset }: {
error: Error & { digest?: string }
reset: () => void
}) {
useEffect(() => {
// Sentry로 전송
Sentry.captureException(error, {
tags: {
errorDigest: error.digest,
errorBoundary: 'app-root'
},
extra: {
userAgent: navigator.userAgent,
timestamp: new Date().toISOString()
}
})
}, [error])
return (
// 오류 UI...
)
}
문제를 재현하기 쉽도록 error.digest와 사용자 환경 정보를 반드시 포함하세요.
일부 팀은 사용자의 최근 이동 경로, 예를 들면 마지막으로 방문한 5개 페이지까지 기록합니다. 문제를 조사할 때 큰 도움이 됩니다.
3. 사용자 친화적인 오류 UI
기술자는 스택 정보를 좋아하지만 사용자는 관심이 없습니다. 사용자가 알고 싶은 것은 다음과 같습니다.
- 무슨 일이 발생했나요?(쉬운 말로 설명)
- 해결할 수 있나요?(명확한 조치 제공)
- 내 데이터가 사라졌나요?(영향 범위 설명)
좋은 오류 UI는 다음과 같아야 합니다.
return (
<div className="error-container">
<h2>페이지를 불러오지 못했습니다</h2>
<p>네트워크가 불안정하거나 서버가 잠깐 쉬고 있을 수 있습니다</p>
<div className="actions">
<button onClick={reset}>다시 시도</button>
<a href="/">홈으로 돌아가기</a>
<a href="/help">고객센터 문의</a>
</div>
<details className="error-details">
<summary>기술 정보(선택 사항)</summary>
<code>{error.digest}</code>
</details>
</div>
)
사용자를 불안하게 만들지 않도록 편안한 어조를 사용하세요. “서버가 잠깐 쉬고 있습니다”가 “500 Internal Server Error”보다 훨씬 친절합니다.
4. 지능형 재시도 전략
앞에서 재시도 횟수 제한을 언급했는데, 몇 가지 팁을 더 살펴보겠습니다.
- 지연 재시도: 즉시 reset하지 말고 서버가 회복할 시간을 주도록 1~2초 기다립니다.
- 지수 백오프: 첫 번째는 1초, 두 번째는 2초, 세 번째는 4초 기다립니다.
- 오류 유형 구분: 네트워크 오류에는 재시도를 권하고, 코드 오류에는 기술 지원팀에 문의하도록 바로 안내합니다.
const [retryCount, setRetryCount] = useState(0)
const [isRetrying, setIsRetrying] = useState(false)
const handleReset = async () => {
setIsRetrying(true)
setRetryCount(prev => prev + 1)
// 지수 백오프: 2^retryCount초
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, retryCount) * 1000)
)
setIsRetrying(false)
reset()
}
5. 환경별 처리
개발 환경과 프로덕션 환경은 오류 표시 방식이 달라야 합니다.
const isDev = process.env.NODE_ENV === 'development'
return (
<div>
<h2>{isDev ? error.message : '문제가 발생했습니다'}</h2>
{isDev && (
<pre>
<code>{error.stack}</code>
</pre>
)}
{!isDev && (
<p>문제가 기록되었으며 최대한 빨리 수정하겠습니다</p>
)}
</div>
)
개발 환경에서는 디버깅하기 쉽도록 전체 스택을 제공합니다. 프로덕션 환경에서는 기술 세부 정보를 노출하지 않고 친절한 안내만 보여 줍니다.
6. 과도하게 사용하지 않기
마지막으로 꼭 기억해야 할 점이 있습니다. Error Boundary는 최후의 안전망이지 주된 오류 처리 수단이 아닙니다.
try-catch로 처리할 수 있는 예상된 오류라면 Error Boundary로 던지지 마세요. 컴포넌트 내부에서 자연스럽게 폴백할 수 있다면 오류 페이지를 실행하지 마세요.
예를 들어 사용자 아바타를 불러오지 못하면 기본 아바타를 표시하면 됩니다. 개인 프로필 페이지 전체를 중단할 필요는 없습니다.
Error Boundary는 국소적으로 처리할 수 없는 진짜 예기치 않은 오류를 위해 남겨 두어야 합니다.
결론
지금까지 많은 내용을 살펴봤지만 핵심은 세 가지입니다.
첫째, Error Boundary는 선택 사항이 아니라 필수입니다. 페이지의 흰 화면으로 인한 사용자 이탈은 생각보다 훨씬 심각합니다. 오류 경계를 제대로 설정하는 데 시간을 조금 투자하면 한밤중에 긴급 호출을 받는 상황을 상당수 피할 수 있습니다.
둘째, 계층별 처리가 중요합니다. error.tsx는 국소 오류를 맡고, global-error.tsx는 전역 안전망이 되며, Server Components에서는 예상된 오류와 예기치 않은 오류를 구분합니다. 명시적으로 처리해야 하는 오류는 직접 처리하고, 오류 경계에 맡겨야 할 오류는 막지 마세요.
셋째, 사용자 경험을 우선해야 합니다. 기술 세부 정보는 모니터링 플랫폼에 남기고, 사용자에게는 언제나 친절하고 실행 가능한 안내를 보여 주세요. “재시도” 버튼은 일시적 오류의 40%를 해결할 수 있으므로 투자 대비 효과가 매우 큽니다.
지금 바로 Next.js 프로젝트에 error.tsx를 추가해 보세요. 루트 디렉터리에서 시작한 뒤 핵심 기능 영역마다 오류 경계를 점진적으로 추가합니다. Sentry 같은 모니터링 도구까지 연동하면 애플리케이션 안정성이 눈에 띄게 개선되는 것을 확인할 수 있습니다.
그리고 global-error.tsx도 잊지 마세요. 거의 실행되지 않지만 안전벨트와 같습니다. 사용할 일이 없기를 바라더라도 반드시 갖춰 두어야 합니다.
Next.js에서 Error Boundary 구현하기
Next.js 애플리케이션에 오류 경계를 추가해 런타임 오류를 우아하게 처리합니다.
- 1
Step 1: error.tsx 파일 생성
app 디렉터리 또는 원하는 라우트 디렉터리에 error.tsx 파일을 만들고 'use client' 지시문을 추가합니다. - 2
Step 2: 오류 처리 컴포넌트 구현
error와 reset 인수를 받는 Error 컴포넌트를 정의하고 친절한 오류 UI를 설계합니다. - 3
Step 3: 오류 리포팅 추가
useEffect에서 오류를 Sentry 같은 모니터링 플랫폼으로 전송하고 error.digest를 기록합니다. - 4
Step 4: 지능형 재시도 구현
재시도 버튼을 추가하고 재시도 횟수를 제한해 일시적 오류에 자동 복구 메커니즘을 제공합니다. - 5
Step 5: global-error.tsx 생성
app 디렉터리에 완전한 HTML 구조를 포함한 global-error.tsx를 만들어 최후의 안전망으로 사용합니다. - 6
Step 6: 오류 유형 구분
Server Components에서 예상된 오류(명시적으로 처리)와 예기치 않은 오류(Error Boundary에 위임)를 구분합니다.
FAQ
error.tsx와 global-error.tsx의 차이는 무엇인가요?
error.tsx는 왜 클라이언트 컴포넌트여야 하나요?
Server Components의 오류도 error.tsx가 포착할 수 있나요?
Error Boundary 대신 try-catch를 사용해야 하는 경우는 언제인가요?
reset() 함수는 어떻게 작동하나요?
4분 읽기 · 게시일: 2026년 1월 6일 · 수정일: 2026년 9월 4일
Next.js 완전 가이드
검색으로 들어왔다면 같은 시리즈의 이전 글이나 다음 글로 이동하는 것이 가장 빠릅니다.
이전
Next.js 404·500 페이지 커스터마이징 완벽 가이드: 구현부터 디자인 최적화까지
not-found.tsx, error.tsx, global-error.tsx의 전체 코드 예제와 디자인 모범 사례, 자주 발생하는 문제 해결법을 통해 Next.js 오류 페이지를 직접 커스터마이징하고 사용자 경험을 높여 이탈률을 낮추는 방법을 안내합니다.
45편 중 29편
다음
Next.js 단위 테스트 실전: Jest + React Testing Library 완전 설정 가이드
Next.js 15 테스트 환경을 처음부터 설정하고 Jest + React Testing Library 구성, Client/Server Components 테스트, Hook 테스트, Mock 기법과 자주 발생하는 문제 해결 방법을 전체 코드 예제와 함께 알아봅니다.
45편 중 31편



댓글
GitHub로 로그인하여 댓글을 남기세요