MMeshOps
AI가 놓치는 엣지 케이스까지: Next.js와 TypeScript 실전
11예상 10입문

유저의 기다림을 위로하다: 로딩과 데이터 없음 상태

데이터를 기다리는 빈 화면을 로딩 스피너와 스켈레톤 UI로 채우고, 검색 결과가 없을 때의 친절한 안내 화면을 구현합니다.

사용자가 버튼을 눌렀을 때 화면이 즉각적으로 변하지 않으면, 사람들은 보통 "앱이 고장 났나?" 하고 생각하며 버튼을 여러 번 누르곤 합니다. 또한, 검색 결과가 없는데 아무런 안내가 없다면 사용자는 혼란에 빠집니다. 이번 에피소드에서는 데이터를 불러오는 동안의 로딩(Loading) 상태와 결과가 없을 때의 빈 화면(Empty) 상태를 어떻게 다루는지 알아봅니다.

왜 로딩과 빈 화면 처리가 중요할까요?

프론트엔드 개발자가 가장 많이 하는 실수 중 하나는 정상적으로 데이터가 불러와졌을 때의 화면(Happy Path)만 신경 쓰는 것입니다. 하지만 실제 서비스에서는 네트워크 지연으로 인해 데이터를 불러오는 데 시간이 걸리거나, 아예 데이터가 존재하지 않는 경우가 훨씬 흔합니다.

이때 적절한 화면 처리를 해주지 않으면 사용자는 텅 빈 하얀 화면을 보게 되며, 서비스에 대한 신뢰를 잃습니다. 따라서 빈 화면(Empty State)로딩 화면(Loading State)은 부가적인 요소가 아니라 핵심 사용자 경험(UX)입니다.

로딩 스피너와 스켈레톤 UI

데이터를 가져오는 동안 사용자에게 "지금 열심히 작업 중입니다"라는 신호를 보내야 합니다. 대표적인 방법으로 로딩 스피너(Loading Spinner)스켈레톤 UI(Skeleton UI)가 있습니다.

로딩 스피너는 빙글빙글 도는 원형 아이콘으로 가장 구현이 쉽습니다. 반면 스켈레톤 UI는 앞으로 데이터가 채워질 공간의 뼈대(스켈레톤)를 미리 희미하게 보여주는 방식입니다. 요즘은 로딩 중이라는 사실보다 '어떤 형태의 정보가 곧 나타날 것인지'를 암시하는 스켈레톤 UI가 더 좋은 평가를 받습니다.

로딩 스피너 vs 스켈레톤 UI

비교 항목로딩 스피너스켈레톤 UI
구현 난이도매우 낮음 (컴포넌트 하나로 해결)다소 높음 (레이아웃에 맞게 뼈대 제작)
사용자 체감 속도보통더 빠름 (화면이 미리 그려짐)
적합한 상황버튼 클릭 후 짧은 동작, 전체 화면 대기목록, 카드 형태의 콘텐츠 불러오기

친절한 '데이터 없음(Empty State)' 화면

데이터베이스에 사용자가 찾는 정보가 없을 때, 화면을 단순히 비워두는 것은 최악의 설계입니다. "검색 결과가 없습니다"라는 텍스트만 덩그러니 놓는 것도 썩 좋지 않습니다.

성공적인 앱들은 텅 빈 화면을 오히려 사용자 액션을 유도하는 기회로 삼습니다. 귀여운 일러스트와 함께 "아직 작성된 글이 없네요. 첫 번째 글을 작성해 보세요!"라는 버튼을 배치하여 사용자가 자연스럽게 다음 행동을 하도록 돕습니다.

React에서 로딩과 데이터 없음 처리하기

export default function PostList({ posts, isLoading }) {
  // 1. 로딩 상태 처리
  if (isLoading) {
    return <p>게시글을 불러오는 중입니다...</p>; // 스피너나 스켈레톤 컴포넌트로 대체 가능
  }

  // 2. 빈 데이터 상태 처리
  if (!posts || posts.length === 0) {
    return (
      <div className="empty-state">
        <p>아직 작성된 게시글이 없습니다.</p>
        <button>첫 게시글 작성하기</button>
      </div>
    );
  }

  // 3. 정상 상태 처리
  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

Next.js에서의 로딩 처리: loading.tsx

React에서는 `useState`로 로딩 상태를 직접 관리해야 했지만, Next.js(App Router)에서는 아주 우아한 해결책을 제공합니다. 바로 파일 시스템 기반의 `loading.tsx`입니다.

특정 페이지 폴더 안에 `loading.tsx` 파일을 만들어두면, Next.js는 서버에서 데이터를 가져오는 동안 이 컴포넌트를 자동으로 먼저 화면에 띄워줍니다. 개발자는 복잡한 상태 관리 로직 없이도 손쉽게 전체 페이지에 로딩 화면을 적용할 수 있습니다.

Next.js의 loading.tsx 작동 방식

tsx
```tsx
// app/posts/loading.tsx
export default function Loading() {
  // 이 화면은 데이터를 가져오는 동안 자동으로 표시됩니다.
  return (
    <div className="flex justify-center items-center h-screen">
      <p className="text-gray-500 font-bold">로딩 중...</p>
    </div>
  );
}
```
  1. 1사용자가 /posts 페이지에 접속합니다.
  2. 2Next.js는 posts/page.tsx의 데이터를 서버에서 가져오기 시작합니다.
  3. 3동시에 posts/loading.tsx의 화면을 사용자에게 즉시 보여줍니다.
  4. 4데이터 로드가 완료되면 loading 화면을 내리고 실제 page.tsx 화면으로 교체합니다.

예외 상황을 방어하는 핵심 습관

AI를 통해 코드를 생성하다 보면, AI는 종종 `데이터가 반드시 배열 형태로 존재한다`고 가정하고 `map` 함수를 사용하는 코드를 던져줍니다. 이때 데이터가 `null`이거나 `undefined`라면 곧바로 앱이 뻗어버리는 치명적인 에러가 발생합니다.

따라서 우리는 항상 데이터가 존재하는지 먼저 확인하는 습관을 들여야 합니다. 이를 옵셔널 체이닝(Optional Chaining, `?.`)이나 조건문으로 방어합니다.

안전한 데이터 렌더링 습관

권장

  • 데이터가 배열인지 확인하거나 기본값([])을 설정하세요.
  • 데이터가 0개일 때 보여줄 Empty State 컴포넌트를 준비하세요.
  • 옵셔널 체이닝(?.)을 사용하여 속성 접근 시 에러를 방어하세요.

주의

  • 데이터가 무조건 들어올 것이라고 맹신하지 마세요.
  • 데이터 검증 없이 바로 `.map()`을 호출하지 마세요.

개념 퀴즈

퀴즈 답을 맞춰야 학습 완료가 됩니다.

다음 중 로딩 및 데이터 없음 상태에 대한 설명으로 올바르지 않은 것은 무엇인가요?

3줄 요약

  1. 1로딩 상태는 스피너나 스켈레톤 UI를 활용하여 시스템이 작동 중임을 시각적으로 알려야 합니다.
  2. 2데이터가 없을 때는 텅 빈 화면 대신 사용자 액션을 유도하는 친절한 Empty State를 제공하세요.
  3. 3Next.js의 loading.tsx를 활용하면 복잡한 상태 관리 없이 로딩 화면을 쉽게 구현할 수 있습니다.