Inflearn brand logo image

인프런 커뮤니티 질문&답변

변재정님의 프로필 이미지
변재정

작성한 질문수

Next.js 까보기: "쓸 줄 아는 개발자"에서 "알고 쓰는 개발자"로

Next.js 에서 Error Handling 하는 법?!

next.js 서버fetch 에러 fallback ui 구현 방법

작성

·

41

0

next 공식문서를 살펴보면 단순 서버 fetch (서버액션x) 관련 에러 핸들링 방법은 딱히 안나와 있는 것 같아 질문드립니다.

만약 SectionA, SectionB, SectionC 서버 컴포넌트 내에서 각각 서버 fetch가 일어난다 가정했을 때 SectionB에서만 요청이 실패했다면 SectionB 영역만 fallback ui를 띄우고 나머지 SectionA, SectionC는 정상적으로 데이터를 렌더링하도록 구현하는 방법이 없을까요?

error-boundary 라이브러리는 클라이언트 환경에서만 사용가능해 서버 컴포넌트에선 사용할 수 없고 next 내부적으로 설계되어있는 에러 바운더리(error.js 등)는 전역에러를 캐치하는 걸로 알고있습니다. 때문에 페이지 내 특정 영역만 fallback ui를 띄우는 용도는 아닌 것 같더라구요.

혹시 다른 방법이 있나 궁금합니다 감사합니다!!

 

답변 2

0

Boaz님의 프로필 이미지
Boaz
지식공유자

답변이 늦었습니다 🙇‍♂
우선 포인트 짚은 좋은 질문 주셔서 감사합니다. 답변 먼저 드릴게요 🤗

  • 가능합니다. “섹션 단위로만 실패 UI”를 띄우는 방법은 두 가지가 있어요.

    1. 섹션 내부에서 에러를 ‘잡고’ 소프트-페일(fallback을 직접 렌더)

    2. App Router의 세그먼트 단위 에러 바운더리를 활용(= error.tsx), 섹션을 Parallel Routes로 분리

말씀하신 방법 중에 error.tsx는 세그먼트 단위로 에러를 잡기에 추가적인 수정(라우팅 구조 관련)이 필요합니다. 우선 간단한 1번부터 자세히 설명드릴게요.


1) 섹션 내부에서 처리(Soft fail)

Server Component 안에서 fetch를 직접 try/catch로 감싸고, 실패 시 그 섹션만 대체 UI를 조건부 렌더링 하는 방법입니다. fetch는 네트워크 오류에서만 throw하고, HTTP 에러는 res.ok로 판별할 수 있어요.

// SectionB.tsx (Server Component)
export default async function SectionB() {
  try {
    const res = await fetch(process.env.API_URL + '/b', { cache: 'no-store' });
    if (!res.ok) {
      // 섹션 전용 fallback
      return <SectionBFallback status={res.status} />;
    }
    const data = await res.json();
    return <SectionBView data={data} />;
  } catch (err) {
    // 네트워크/파싱 등 예외
    return <SectionBFallback />;
  }
}

이 방식은 A, C는 정상 렌더, B만 fallback이 됩니다. 서버 액션이 아니어도 문제없어요. (Next 공식 가이드도 렌더 이후 비동기/이벤트성 에러는 바운더리가 못 잡으니 직접 처리하라고 안내합니다. (Next.js))

장점: 구조 변경 없음.
단점: “던지는(throw) 에러”를 경계로 분리해서 다루는 건 아님(즉, 바운더리가 아니라 “조건부 렌더”).


2) 진짜 바운더리: Parallel Routes + error.tsx

Next.js는 세그먼트 단위로 에러 바운더리를 둡니다. 같은 URL 안에서도 Parallel Routes(@slot) 를 쓰면 각 슬롯에 개별 error.tsx 를 둘 수 있어요. B만 실패해도 B 슬롯만 에러 UI가 나옵니다. (Next.js)

예시 구조:

app/dashboard/
  page.tsx                  // 전체 레이아웃에서 슬롯을 배치
  @a/page.tsx               // SectionA
  @a/default.tsx
  @b/page.tsx               // SectionB
  @b/error.tsx              // B 전용 에러 UI
  @b/default.tsx
  @c/page.tsx               // SectionC
  @c/default.tsx

page.tsx(부모)에서 슬롯을 배치:

export default function Dashboard({
  a, b, c,
}: { a: React.ReactNode; b: React.ReactNode; c: React.ReactNode }) {
  return (
    <main className="grid gap-6">
      {a}
      {b}  {/* B만 실패하면 여기만 error.tsx로 대체 */}
      {c}
    </main>
  );
}

@b/error.tsx반드시 Client Component여야 하고, reset()으로 재시도 버튼을 만들 수 있습니다. (Next.js)

'use client';

export default function Error({
  error,
  reset,
}: { error: Error & { digest?: string }, reset: () => void }) {
  return (
    <div>
      <h2>섹션 B에서 오류가 발생했습니다.</h2>
      <button onClick={() => reset()}>다시 시도</button>
    </div>
  );
}

장점: 진짜 “경계 격리”. 스트리밍 중에도 해당 슬롯만 대체 UI로 전환.
단점: 라우팅(폴더) 구조를 바꿔야 함.

참고: 말씀해주신 것처럼, 일반적인 커스텀 <ErrorBoundary> 컴포넌트(react-error-boundary 등)를 JSX로 감싸는 것만으로는 서버 컴포넌트에서 던진 에러를 못 잡는 제약이 있습니다. App Router에선 세그먼트 error.tsx 를 두어야 안정적으로 잡혀요. (GitHub)


어떤 방식을 고를까?

  • 빠른 적용/섹션별 단순 실패 처리 → 1) Soft fail (try/catch + 조건부 렌더)

  • 스트리밍/격리/리트라이까지 갖춘 정석 분리 → 2) Parallel Routes + error.tsx

공식 문서 요약:

  • 에러 바운더리는 세그먼트 레벨에서 중첩 배치 가능(그 아래만 잡음), error.tsx클라이언트 컴포넌트여야 함. (Next.js)

  • Parallel Routes는 각 슬롯별 독립 로딩/에러 상태를 제공. (Next.js)

지금 쓰고 계신 페이지 구조에 맞춰 해결책을 선택하실 수 있을거여요. 리팩터링 없이 Soft fail 패턴으로 우선 적용하거나, Parallel Routes로 분리하는 파일 구조를 적용하는 방식으로요.

0

안녕하세요, 인프런 AI 인턴입니다. 변재정님.

Next.js에서 서버 컴포넌트의 각 섹션에 대해 개별적인 에러 핸들링 및 폴백 UI를 구현하려는 경우, 기본적인 서버 컴포넌트의 에러는 전역으로 처리되기 때문에 다소 제한적일 수 있습니다. 그러나 이를 구현할 몇 가지 방법이 있습니다.

개별적인 서버 요청 처리

  1. Promise.allSettled() 사용:
    • 여러 개의 서버 요청을 병렬적으로 처리하고, 각각의 요청 결과를 확인하여 성공과 실패를 구분할 수 있습니다.
    • 실패한 요청에 대해서만 폴백 UI를 렌더링할 수 있는 로직을 작성할 수 있습니다.
export default async function PageComponent() {
  const [resultA, resultB, resultC] = await Promise.allSettled([
    fetchSectionA(),
    fetchSectionB(),
    fetchSectionC()
  ]);

  return (
    <div>
      {resultA.status === "fulfilled" ? <SectionA data={resultA.value} /> : <FallbackA />}
      {resultB.status === "fulfilled" ? <SectionB data={resultB.value} /> : <FallbackB />}
      {resultC.status === "fulfilled" ? <SectionC data={resultC.value} /> : <FallbackC />}
    </div>
  );
}
  1. 에러 핸들링 함수 사용:
    • 각각의 fetch 함수 내에서 try-catch 문을 사용하여 에러 발생 시 해당 에러에 적절히 대응할 수 있습니다. 각 섹션의 컴포넌트에서 이 처리된 결과를 받아 렌더링하면 됩니다.
async function fetchWithFallback(fetchFunction, fallbackData = null) {
  try {
    const response = await fetchFunction();
    return response;
  } catch (error) {
    console.error('Fetch failed:', error);
    return fallbackData; // 사용할 기본 데이터
  }
}

서버 컴포넌트 에러 핸들링

Next.js의 current 커스텀 에러 핸들링 기능으로는 서버 컴포넌트 내에서 부분적인 에러 바운더리 설정이 제한적이므로, 위와 같이 Promises와 개별 fetch 처리 함수를 사용하여 각 섹션별 에러를 개별적으로 핸들링하셔야 합니다.

또한, 이미 알고 계신 것처럼 클라이언트 환경에서의 ErrorBoundary와 유사한 기능은 서버 컴포넌트에서 직접적으로 지원되지 않으며, 레이아웃 경로 내의 error.js 파일은 페이지 수준의 에러를 처리합니다.

관련된 논의를 참고하기 위해 Parallel Route를 사용한 모달에 대해 질문을 들어가 확인해보세요. 이 게시물에서도 에러와 관련된 Next.js의 라우트와 관련된 설명이 있습니다.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

변재정님의 프로필 이미지
변재정

작성한 질문수

질문하기