본문으로 건너뛰기
개발 머꼬
개발 노트React
hohyeon.dev20

Suspense 하나로 페이지 전체를 감쌌다가 로딩이 계속 겹쳐 보인 이유

  • #Engineering Note
  • #Next.js
  • #Performance
  • #React

문제 발생

페이지 전체를 하나의 <Suspense>로 감쌌더니, 페이지 안의 작은 위젯 하나만 데이터를 다시 불러올 때도 화면 전체가 로딩 스피너로 바뀌는 문제가 있었습니다.

<Suspense fallback={<FullPageSpinner />}>
  <Header />
  <MainContent />  {/* 이것만 다시 로딩되어도 */}
  <Sidebar />
</Suspense>

원인 분석

Suspense는 그 경계 안에 있는 컴포넌트 중 하나라도 아직 준비되지 않았으면(데이터를 기다리는 중이면), 그 경계 전체를 fallback으로 대체합니다 — 경계 안의 일부만 로딩 중이어도 전체가 로딩 UI로 바뀝니다. Suspense boundary를 크게 하나로 잡으면, 그 안에 있는 어떤 하위 컴포넌트가 다시 데이터를 가져오든 항상 화면 전체가 깜빡이며 스피너로 바뀝니다.

해결 방안

경계를 세분화해 정말 로딩이 필요한 부분만 로딩 UI를 보여주도록 만듭니다.

<Header />  {/* Suspense 밖 — 항상 즉시 보임 */}
<div className="layout">
  <Suspense fallback={<MainContentSkeleton />}>
    <MainContent />
  </Suspense>
  <Suspense fallback={<SidebarSkeleton />}>
    <Sidebar />
  </Suspense>
</div>

이렇게 나누면 MainContent가 다시 데이터를 불러오는 동안에도 HeaderSidebar는 그대로 보이고, MainContent 영역만 자기 자리에서 스켈레톤으로 바뀝니다 — 화면 전체가 깜빡이지 않습니다.

  1. 함께 나타나야 자연스러운 것들은 같은 경계로 묶습니다 — 예를 들어 카드 안의 제목과 이미지가 따로따로 나타나면 오히려 어색하다면, 그 카드 전체를 하나의 Suspense로 묶는 게 맞습니다.
  2. 독립적으로 로딩될 수 있는 영역은 분리합니다 — 사이드바와 메인 콘텐츠처럼 서로 다른 데이터 소스를 쓰는 영역은 각자 경계를 갖게 하면 한쪽이 느려도 다른 쪽은 먼저 보여줄 수 있습니다.
  3. Next.js App Router의 loading.tsx는 라우트 세그먼트 단위의 암묵적 Suspense 경계입니다 — 페이지 단위의 큰 로딩은 loading.tsx로, 페이지 안의 특정 컴포넌트 단위의 세밀한 로딩은 명시적 <Suspense>로 나누는 조합이 흔한 패턴입니다.
  4. 항상 "이 컴포넌트가 로딩 중일 때 사용자가 나머지 화면을 계속 볼 수 있는 게 나은가, 아니면 전체를 같이 기다리는 게 나은가"를 기준으로 경계를 정합니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.