본문 바로가기
C.W.K.
Stream
Lesson 09 of 10 · published

로딩·오류·404 경계

~22 min · loading, error, not-found, Suspense

Level 0호기심
0 XP0/68 lessons0/11 achievements
0/120 XP to next level120 XP to go0% complete

세 파일이 서로 다른 실패 시간을 맡아

파일역할실행 위치
loading.tsx페이지를 Suspense로 감싸 데이터가 준비되는 동안 보여 줘.서버 또는 클라이언트
error.tsx해당 구간과 하위 트리에서 난 오류를 잡아.클라이언트 필수
not-found.tsx해당 구간에서 notFound()를 실행했을 때 보여 줘.서버 또는 클라이언트

한 화면에 몰아서 조건문으로 처리하지 않아도 파일 위치가 경계의 범위를 정해 줘. 더 안쪽에 파일을 둘수록 실패가 영향을 주는 범위도 좁아져.

루트 레이아웃에는 별도 안전망이 필요해

일반 error.tsx는 자신과 같은 구간의 레이아웃에서 난 오류를 잡지 못해. 루트 레이아웃까지 깨질 수 있는 경우에는 app/global-error.tsx를 둬. 이 파일은 망가진 루트 문서 전체를 대신하므로 자기 <html><body>를 직접 반환해야 해.

존재하지 않는 데이터는 명시적으로 끝내

서버 컴포넌트에서 next/navigationnotFound()를 실행하면 렌더링을 중단하고 가장 가까운 not-found.tsx로 넘어가. HTTP 404 상태 코드는 프레임워크가 처리해. 빈 화면을 정상 응답처럼 돌려주는 것보다 의미와 상태가 함께 맞아.

각 실패를 데이터 대기, 예상 가능한 부재, 예상 밖 오류로 분류해. 대기는 loading.tsx, 없는 자원은 notFound(), 예외는 error.tsx로 보내면 상태 코드와 사용자 행동이 맞아. 재시도 버튼이 실제로 안전한지, 오류 경계가 어느 레이아웃까지 보존하는지도 시험해. 각 경계에 일부러 지연과 예외를 넣고 어느 레이아웃까지 살아 남는지 기록해. 오류를 고친 뒤 다시 시도할 때 같은 입력과 이동 맥락이 유지되는지도 봐. 모든 오류를 친절한 200 화면으로 바꾸면 모니터링과 검색 엔진은 실패를 알아채지 못해. 반대로 사용자가 고칠 수 있는 검증 오류를 예외로 던지면 전체 경계가 무너져. 실패의 기술적 종류보다 다음 행동이 같은 사건끼리 묶는 게 좋아. 느린 Promise, 존재하지 않는 ID, 렌더 예외를 각각 만들어 세 파일이 다른 상태와 화면을 내는지 확인해. 같은 구간의 레이아웃에서 오류를 던져 일반 error.tsx가 잡지 못하는 것도 보고, 루트 실패에서는 global-error.tsx가 독립 문서를 만드는지 시험해.

Code

자동으로 연결되는 로딩 골격·tsx
// app/dashboard/loading.tsx
export default function Loading() {
  return (
    <div className="animate-pulse space-y-3 p-6">
      <div className="h-6 w-1/3 rounded bg-gray-200" />
      <div className="h-4 w-full rounded bg-gray-200" />
      <div className="h-4 w-5/6 rounded bg-gray-200" />
    </div>
  );
}

// app/dashboard/page.tsx — 느린 데이터가 경계를 실행해
export default async function Dashboard() {
  const data = await fetchSlow();
  return <DashboardContent data={data} />;
}
구간별 오류 경계·tsx
// app/dashboard/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="p-6 text-center">
      <h2 className="text-lg font-semibold">Something went wrong</h2>
      <p className="text-sm text-gray-500 mt-1">{error.message}</p>
      <button
        onClick={() => reset()}
        className="mt-4 rounded bg-blue-600 px-4 py-2 text-white"
      >
        Try again
      </button>
    </div>
  );
}
서버 컴포넌트에서 404 내기·tsx
import { notFound } from 'next/navigation';

export default async function Page({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;
  const post = await getPost(id);
  if (!post) notFound();
  return <Article post={post} />;
}

External links

Exercise

라우트에 loading.tsx, error.tsx, not-found.tsx를 추가해. 의도적 예외와 없는 ID를 각각 실행하고 오류 경계와 HTTP 404가 맞게 나타나는지 확인해.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.