UX를 완성하는 Loading / Error 처리 방법

Dam·2026년 3월 19일

[Next.js]

목록 보기
19/28
post-thumbnail

사용자 경험(UX)을 크게 좌우하는 요소 중 하나는 로딩 상태와 에러 처리 UI이다.
특히 Next.js(App Router)에서는 이를 파일 단위로 구조화하여 매우 직관적으로 관리할 수 있다.

처음에는 단순한 보조 UI라고 생각했지만, 직접 적용해보면서
Streaming만으로는 UX가 완성되지 않고, fallback UI 설계가 반드시 필요하다는 점을 체감하게 되었다.

이 글에서는 다음 내용을 정리한다.

  • loading.tsx
  • error.tsx
  • not-found.tsx
  • Suspense 기반 로딩 처리
  • Streaming과 UI fallback의 관계

1. loading.tsx

개념

loading.tsx는 해당 라우트(segment)가 로딩 중일 때 자동으로 보여지는 UI이다.

즉, 데이터 fetching 또는 서버 컴포넌트가 아직 준비되지 않았을 때
사용자에게 보여줄 fallback UI를 정의하는 역할을 한다.

특징

  • 해당 폴더(라우트 segment)에 자동 적용
  • 별도의 import 없이 Next.js가 자동 연결
  • 내부적으로 Suspense fallback 역할 수행

예시

// app/posts/loading.tsx
export default function Loading() {
  return <p>Loading posts...</p>;
}

핵심 포인트

  • 페이지 전환 시 깜빡임 없이 자연스러운 UX 제공
  • Skeleton UI와 함께 사용하는 것이 일반적

👉 실제로 적용해보니 단순 텍스트보다 Skeleton UI가 훨씬 자연스럽게 느껴졌다.


2. error.tsx

개념

error.tsx는 해당 라우트에서 에러 발생 시 보여주는 UI이다.

React의 Error Boundary 기반으로 동작하며,
예상치 못한 에러 상황에서도 UI를 유지할 수 있게 해준다.

특징

  • 반드시 클라이언트 컴포넌트 ('use client')
  • reset() 함수를 통해 에러 복구 가능

예시

'use client';

export default function Error({ error, reset }: { error: Error; reset: () => void }) {
  return (
    <div>
      <h2>Something went wrong!</h2>
      <button onClick={() => reset()}>Try again</button>
    </div>
  );
}

핵심 포인트

  • 에러 발생 시에도 UI가 깨지지 않음
  • 사용자에게 재시도 기회 제공

👉 단순히 에러를 보여주는 것보다, 다시 시도할 수 있게 만드는 게 훨씬 중요하다고 느꼈다.


3. not-found.tsx

개념

not-found.tsx는 존재하지 않는 경로이거나
notFound()가 호출될 때 보여지는 UI이다.

특징

  • 404 페이지를 라우트 단위로 정의 가능
  • 서버 컴포넌트에서도 쉽게 트리거 가능

예시

// app/posts/not-found.tsx
export default function NotFound() {
  return <h2>Post Not Found</h2>;
}
// 사용 예시
import { notFound } from 'next/navigation';

export default async function Page({ params }) {
  const data = await fetchData(params.id);

  if (!data) {
    notFound();
  }

  return <div>{data.title}</div>;
}

핵심 포인트

  • 페이지 단위가 아닌 segment 단위 404 처리
  • SEO 및 UX 모두에 유리

👉 기존에는 전역 404만 생각했는데, 이렇게 나눌 수 있다는 점이 인상적이었다.


4. Suspense 기반 로딩 처리

개념

React의 Suspense를 활용하면
컴포넌트 단위로 로딩 상태를 제어할 수 있다.

예시

import { Suspense } from 'react';
import PostList from './PostList';

export default function Page() {
  return (
    <Suspense fallback={<p>Loading posts...</p>}>
      <PostList />
    </Suspense>
  );
}

핵심 포인트

  • 페이지 전체가 아니라 부분 단위 로딩 처리 가능
  • 느린 컴포넌트만 분리해서 UX 개선 가능

👉 loading.tsx만으로는 부족하고, Suspense를 같이 써야 세밀한 제어가 가능했다.


5. Streaming vs UI Fallback

Streaming이란?

서버에서 데이터를 모두 준비한 후 보내는 것이 아니라,
준비된 UI부터 점진적으로 클라이언트에 전달하는 방식이다.


직접 적용하면서 느낀 점

Streaming을 적용하면 페이지가 빠르게 보이긴 했지만,
실제로는 UX가 완전히 좋아졌다고 느껴지지는 않았다.

특히 아래와 같은 상황이 있었다.

문제점

  • 어떤 부분이 아직 로딩 중인지 알기 어려움
  • UI가 중간중간 늦게 나타나면서 흐름이 끊기는 느낌
  • fallback 없이 콘텐츠가 갑자기 바뀌는 느낌

결론

👉 Streaming만으로는 부족하고,
👉 반드시 fallback UI와 같이 설계해야 한다.

즉,

  • loading.tsx
  • Suspense fallback

이 둘이 함께 있어야 자연스러운 UX가 만들어진다.


동작 흐름

[사용자 요청]
   ↓
[Streaming 시작]
   ↓
[Fallback UI 먼저 렌더링]
   ↓
[데이터 준비 완료]
   ↓
[실제 UI로 점진적 교체]

6. 핵심 정리

  • loading.tsx → 자동 fallback UI
  • error.tsx → Error Boundary
  • not-found.tsx → 라우트 단위 404 처리
  • Suspense → 컴포넌트 단위 로딩 제어
  • Streaming → 성능 개선, UX 완성은 fallback이 담당

한 줄 정리

Streaming은 빠르게 보여주기 위한 것이고,
실제 사용자 경험은 fallback UI가 완성한다.


7. 실무에서 적용할 수 있는 포인트 정리

  • loading.tsx에는 Skeleton UI를 사용하는 것이 좋다
  • Suspense는 "느린 컴포넌트 기준"으로 분리하는 것이 효과적이다
  • error.tsx에는 retry UX를 반드시 포함하는 것이 좋다
  • not-found는 SEO까지 고려해서 설계해야 한다

마무리

이번 내용을 정리하면서 느낀 점은,
로딩과 에러 처리는 단순히 "예외 상황 처리"가 아니라
사용자 흐름을 끊지 않기 위한 설계 요소라는 것이다.

Next.js App Router는 이런 부분을 구조적으로 나눌 수 있게 해주기 때문에,
기능 구현뿐 아니라 UX까지 같이 고민하게 만드는 도구라고 느꼈다.

profile
⏰ Good things take time

0개의 댓글