React에서 스켈레톤 UI 적용하기 (TSX + TailwindCSS 기반)

두밥비·2025년 5월 24일

article

목록 보기
16/23
post-thumbnail

사용자 경험을 고려한 로딩 전략은 프론트엔드 개발에서 점점 더 중요해지고 있습니다. 스켈레톤 UI는 이러한 맥락에서 등장한 대표적인 UI 패턴 중 하나입니다. 로딩 스피너보다 시각적으로 더 안정감을 주며, 콘텐츠가 준비되고 있다는 명확한 피드백을 제공합니다.


1. 스켈레톤 UI란?

스켈레톤 UI(Skeleton UI)는 실제 콘텐츠가 로딩되기 전, 콘텐츠의 레이아웃 구조를 미리 시각적으로 보여주는 UI입니다. 일반적으로 회색의 박스 형태로 콘텐츠의 형태를 대신 표현하며, 흔히 animate-pulse 애니메이션을 통해 미묘한 깜빡임 효과를 줍니다.

왜 필요할까?

  • 사용자에게 콘텐츠의 대략적인 위치와 모양을 먼저 보여주어 레이아웃 붕괴 현상을 방지할 수 있습니다.
  • 갑작스러운 콘텐츠 등장(Pop-in 현상)을 막고, UX의 연속성을 확보할 수 있습니다.
  • 긴 로딩 시간 동안 아무것도 없는 화면을 보는 것보다 시각적 기대감을 줄 수 있어 이탈률을 줄이는 데 기여합니다.

2. 어떤 상황에서 유용할까?

  • API 응답이 300ms 이상 지연될 가능성이 있는 경우
  • 이미지나 콘텐츠가 비동기로 로드되는 컴포넌트일 때
  • 리스트, 카드, 피드 등 반복적인 콘텐츠가 로딩되는 경우
  • SSR/CSR 전환 중의 화면 깜빡임을 막고 싶을 때

이처럼 스켈레톤 UI는 단순히 ‘심심한 로딩화면’의 대체재가 아니라, 실제로 사용자 경험에 큰 영향을 주는 중요한 요소입니다.


3. 기본 Skeleton 컴포넌트 만들기

TailwindCSS의 animate-pulse 클래스는 기본적인 깜빡임 효과를 제공합니다. 여기에 필요한 width/height/rounded 클래스 등을 조합해 유연한 Skeleton 컴포넌트를 만들 수 있습니다.

// components/Skeleton.tsx
interface SkeletonProps {
  className?: string;
}

const Skeleton = ({ className = '' }: SkeletonProps) => {
  return <div className={`bg-gray-200 rounded-md animate-pulse ${className}`} />;
};

export default Skeleton;

해당 컴포넌트는 다음과 같은 방식으로 다양한 곳에 재사용할 수 있습니다:

<Skeleton className="w-32 h-4 mb-2" />
<Skeleton className="w-16 h-3 mt-1" />
<Skeleton className="h-10 w-full rounded-lg" />

4. 카드형 UI에 Skeleton 적용하기

대표적인 케이스 중 하나는 카드 UI입니다. 로딩 중임에도 불구하고 레이아웃이 유지되어 사용자에게 일관된 인상을 주는 것이 핵심입니다.

// components/StoreCardSkeleton.tsx
import Skeleton from './Skeleton';

export default function StoreCardSkeleton() {
  return (
    <div className="flex gap-3 p-3 bg-white rounded-2xl border border-gray-100 shadow-sm w-full max-w-md">
      <Skeleton className="w-24 h-24 rounded-xl flex-shrink-0" />
      <div className="flex flex-col justify-between flex-1 py-1">
        <div>
          <Skeleton className="w-32 h-4 mb-2" />
          <Skeleton className="w-20 h-3 mb-1" />
          <Skeleton className="w-24 h-3" />
        </div>
        <Skeleton className="w-16 h-4 mt-2" />
      </div>
    </div>
  );
}

5. 조건부 렌더링을 통한 적용

페이지 상에서는 isLoading 여부를 기준으로 스켈레톤 컴포넌트를 렌더링합니다.

{isLoading
  ? Array.from({ length: 4 }).map((_, i) => <StoreCardSkeleton key={i} />)
  : storeList.map((store) => <StoreCard key={store.id} {...store} />)}

이처럼 같은 구조의 컴포넌트를 뼈대로 먼저 보여준 뒤, 실제 데이터를 연결하는 방식은 사용자에게 일관된 시각적 경험을 제공합니다.


6. 다양한 구조의 Skeleton 만들기

스켈레톤 UI는 카드에만 국한되지 않습니다. 다양한 UI 요소에 적용할 수 있으며, 예시 몇 가지를 아래에 소개합니다.

리스트 아이템

<div className="flex gap-4 items-center mb-4">
  <Skeleton className="w-10 h-10 rounded-full" />
  <div className="flex-1">
    <Skeleton className="w-1/2 h-4 mb-1" />
    <Skeleton className="w-1/3 h-3" />
  </div>
</div>

버튼 UI

<Skeleton className="w-32 h-10 rounded-full" />

폼 입력창

<Skeleton className="h-10 w-full rounded-md" />

7. 커스터마이징 팁

컬러 변경

기본 색상은 bg-gray-200이지만, 더 밝게(bg-gray-100) 또는 어둡게(bg-gray-300, bg-gray-400) 조절할 수 있습니다. 다크 모드 대응은 dark:bg-gray-700 등을 추가합니다.

애니메이션 변경

animate-pulse 외에도 커스텀 shimmer 애니메이션을 직접 만들 수 있습니다.

@keyframes shimmer {
  0% { background-position: -400px 0; }
  100% { background-position: 400px 0; }
}

접근성

스크린리더가 읽지 않도록 aria-hidden, role="presentation" 등을 추가합니다.

<div aria-hidden="true" role="presentation" className="animate-pulse bg-gray-200 ..." />

8. 라이브러리 사용 vs 직접 구현

직접 구현

  • 커스터마이징 자유도 높음
  • Tailwind와의 연동 쉬움
  • 다만 각 컴포넌트를 일일이 구현해야 하므로 반복 작업 발생 가능

라이브러리 사용

react-loading-skeleton을 예로 들 수 있습니다.

npm install react-loading-skeleton
import Skeleton from 'react-loading-skeleton';

<Skeleton height={20} width={100} borderRadius={8} />

장점은 빠르게 적용 가능하다는 점, 단점은 커스터마이징 제한과 Tailwind와의 스타일 격리 문제입니다.


9. 실무 적용 시 고려사항

  • 데이터 상태 관리와 함께 연동: React Query나 SWR과 같은 비동기 관리 라이브러리와 함께 사용할 때 isLoading, isFetching 등의 상태를 정확하게 구분하는 것이 중요합니다.
  • 레이아웃 유지를 위한 사이즈 고정: 콘텐츠 크기를 예측할 수 없는 경우, skeleton 요소에 min-width, min-height 등을 적용하여 스켈레톤이 레이아웃을 잡아주도록 합니다.
  • Suspense와 병행 사용: React의 Suspensefallback을 활용하면 컴포넌트 단위의 스켈레톤 처리도 가능합니다.

10. 확장 아이디어

  • shimmer 애니메이션 직접 구현
  • Skeleton 전역 스타일 정의 (theme.ts 또는 style-guide.ts)
  • Skeleton List 유틸 함수로 컴포넌트 간 재사용성 향상
  • Storybook 내에 Loading 상태 스토리 포함하여 디자이너와 공유
  • 이미지 로딩 대기 시 onLoad 이벤트 기반으로 스켈레톤 제거

마무리

스켈레톤 UI는 이제 더 이상 단순한 시각적 장치가 아닌, 전체 UX를 좌우하는 핵심 구성 요소입니다. 특히 API 연동이 많은 SPA 환경에서는 사용자의 인내심을 유지시키는 데 매우 효과적입니다. TailwindCSS와 React 조합만으로도 쉽고 유연하게 구성할 수 있으니, 프로젝트에 반드시 도입해보길 추천합니다.

profile
개발새발

0개의 댓글