Develop/NextJS

[Next.js] Suspense를 이용한 Skeleton UI 적용하기

2024. 8. 19. 23:26

 

 

위와 같은 페이지에 리스트 데이터가 렌더링 되기 전까지 스켈레톤을 보여주려고 한다. Next.js에서도 React와 마찬가지로 Suspense를 사용하여 Skeleton UI를 적용할 수 있다.

 

 

 

 

 

Skeleton 컴포넌트 구현하기

(app/components/Skeleton.tsx)

import styles from '@/styles/skeleton.module.scss'

interface SkeletonProps {
  w: number
  h: number
  radius?: number
  wUnit?: string
  style?: React.CSSProperties
}

export default function Skeleton({
  w,
  h,
  wUnit = 'px',
  radius = 8,
  style,
}: SkeletonProps) {
  const size: React.CSSProperties = {
    width: `${w}${wUnit}`,
    height: `${h}px`,
    borderRadius: `${radius}px`,
  }

  return <div className={styles.container} style={{ ...size, ...style }} />
}
(app/styles/skeleton.module.scss)

@-webkit-keyframes skeleton-gradient {
    0% {
        background-color: rgba(165, 165, 165, 0.1);
    }

    50% {
        background-color: rgba(165, 165, 165, 0.3);
    }

    100% {
        background-color: rgba(165, 165, 165, 0.1);
    }
}

@keyframes skeleton-gradient {
    0% {
        background-color: rgba(165, 165, 165, 0.1);
    }

    50% {
        background-color: rgba(165, 165, 165, 0.3);
    }

    100% {
        background-color: rgba(165, 165, 165, 0.1);
    }
}

.container {
    -webkit-animation: skeleton-gradient 1.8s infinite ease-in-out;
    animation: skeleton-gradient 1.8s infinite ease-in-out;
}

 

 

 

목록 대신 보여줄 Skeleton 만들기

(app/list/ListSkeleton.tsx)

import Skeleton from '@/components/Skeleton'
import { v4 as uuidv4 } from 'uuid'

export default function ListSkeleton() {
  return (
    <div>
      {Array.from({ length: 10 }).map(() => (
        <div key={uuidv4()}>
          <Skeleton w={150} h={210} />
          <Skeleton w={150} h={20} radius={4} style={{ marginTop: '6px' }} />
          <Skeleton w={150} h={24} radius={4} style={{ marginTop: '3px' }} />
        </div>
      ))}
    </div>
  )
}

 

 

 

목록 페이지에 Skeleton 적용하기

(app/list/page.tsx)

import { Suspense } from 'react'
import ListSkeleton from '../ListSkeleton'
import BookList from './BookList'

export default async function Page() {
  return (
    <div>
      <Suspense fallback={<ListSkeleton />}>
        <BookList />
      </Suspense>
    </div>
  )
}

 

(app/list/BookList.tsx)

import BookInfoCard from '@/components/BookInfoCard'
import { formatBooksInfo } from '../_utils/formatBookInfo'
import bookApi from '../services'

export default async function BookList() {
  const { totalResults, item } = await bookApi.getList()
  const books = formatBooksInfo(item)

  return (
    <div>
      {books &&
        books.map((book) => <BookInfoCard book={book} key={book.isbn} />)}
    </div>
  )
}

 

 

결과물!

 

흰 화면만 보여주다가 스켈레톤을 적용하니 한층 더 보기 좋아졌다!