반응형

Next.js App Router를 쓰다 보면 loading.tsx와 <Suspense>를 함께 만나게 됩니다. 둘 다 “로딩 중에 무엇을 보여줄 것인가”를 다루기 때문에 처음에는 같은 역할처럼 보입니다.

하지만 실제로는 적용되는 위치가 다릅니다. loading.tsx는 Next.js 라우트 세그먼트 단위의 로딩 UI이고, <Suspense>는 React 컴포넌트 트리 안에서 특정 영역을 기다리게 만드는 경계입니다.

핵심은 간단합니다. “페이지 전체를 기다릴 것인가, 페이지 안의 일부만 기다릴 것인가”를 구분하는 것입니다.

loading.tsx는 라우트 단위의 기본 로딩 화면

loading.tsx는 Next.js App Router의 파일 규칙입니다. 특정 라우트 세그먼트에 loading.tsx를 두면, 해당 세그먼트의 페이지가 준비되는 동안 Next.js가 자동으로 이 UI를 보여줍니다.

예를 들어 app/posts/loading.tsx를 만들면 /posts 페이지가 준비되는 동안 사용자는 이 파일에서 정의한 로딩 화면을 보게 됩니다.

// app/posts/loading.tsx
export default function Loading() {
  return <p>페이지를 준비하는 중...</p>;
}

이 방식은 라우트 전환 중 사용자가 빈 화면을 보지 않게 해주는 기본 안전망에 가깝습니다. 페이지나 하위 레이아웃이 아직 준비되지 않았을 때 최소한의 피드백을 줄 수 있습니다.

Suspense는 컴포넌트 단위의 세밀한 경계

반면 <Suspense>는 React가 제공하는 기능입니다. 페이지 안에서 특정 컴포넌트만 따로 감싸고, 그 컴포넌트가 준비될 때까지 별도의 fallback UI를 보여줄 수 있습니다.

import { Suspense } from 'react';

async function PostsList() {
  const res = await fetch('https://example.com/api/posts', {
    cache: 'no-store',
  });
  const posts = await res.json();

  return (
    <ul>
      {posts.map((post: { id: number; title: string }) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

function PostsFallback() {
  return <p>게시글을 불러오는 중...</p>;
}

export default function Page() {
  return (
    <main>
      <h1>Posts</h1>
      <Suspense fallback={<PostsFallback />}>
        <PostsList />
      </Suspense>
    </main>
  );
}

이렇게 하면 페이지 전체를 막지 않고, 게시글 목록처럼 느릴 수 있는 부분만 따로 기다리게 만들 수 있습니다.

둘의 차이는 경계의 위치다

loading.tsx는 Next.js 라우팅 레벨에서 동작합니다. 라우트 세그먼트 전체가 아직 준비되지 않았을 때 보여줄 화면을 정합니다.

<Suspense>는 React 렌더링 레벨에서 동작합니다. 페이지 내부의 특정 컴포넌트 트리가 준비되지 않았을 때 fallback을 보여줍니다.

따라서 둘 중 하나만 선택하는 문제가 아닙니다. loading.tsx는 라우트 전환의 기본 안전망으로 두고, <Suspense>는 페이지 안에서 느린 영역을 세밀하게 나눌 때 사용하는 식으로 함께 볼 수 있습니다.

Suspense 경계는 너무 높아도, 낮아도 문제다

Suspense 경계를 너무 높게 잡으면 작은 지연도 페이지 전체 로딩처럼 보입니다. 실제로는 추천 목록 하나만 늦는 상황인데, 사용자는 화면 전체가 멈춘 것처럼 느낄 수 있습니다.

반대로 경계를 너무 낮게 잡으면 UI가 조각조각 나타납니다. 카드, 리스트, 사이드바가 각자 따로 로딩되면서 화면이 산만해지고, 레이아웃 안정감도 떨어질 수 있습니다.

그래서 실무에서는 “이 영역이 독립적으로 보여도 되는가?”를 먼저 물어보는 편이 좋습니다. 본문, 요약, 핵심 정보처럼 함께 읽혀야 하는 영역은 하나의 경계로 묶는 편이 낫고, 추천 목록, 부가 정보, 댓글처럼 늦게 나타나도 괜찮은 영역은 별도의 Suspense 경계로 나눌 수 있습니다.

정리

loading.tsx는 라우트 단위의 로딩 UI입니다. 사용자가 페이지를 이동할 때 빈 화면을 보지 않도록 도와주는 기본 장치입니다.

<Suspense>는 컴포넌트 단위의 로딩 경계입니다. 페이지 안에서 특정 섹션만 따로 기다리게 만들어 더 세밀한 사용자 경험을 설계할 수 있습니다.

결국 중요한 것은 로딩을 무조건 숨기는 것이 아닙니다. 사용자에게 어떤 정보를 먼저 보여주고, 어떤 영역은 나중에 보여줘도 되는지 판단하는 것입니다. 좋은 로딩 UI는 단순히 빠르게 보이는 화면이 아니라, 사용자가 자연스럽게 이해할 수 있는 단위로 화면을 드러내는 설계입니다.

참고

반응형