[TIL] 241109_Next.js: Suspense와 Skeleton - Day15

지코·2024년 11월 9일
0

Today I Learned

목록 보기
49/66
post-thumbnail
export default function Page() {
	return (
		<h1>Suspense와 스켈레톤 🤓</h1>
    );
}

⚡️ 컴포넌트 스트리밍 적용하기

지난 포스트에서 살펴 본 📁 loading.tsx 파일을 이용해 스트리밍을 적용하는 방식은 페이지 컴포넌트 단위로만 적용이 가능해, 일반 컴포넌트에는 적용이 불가능하다는 특징을 가지고 있다.

따라서 일반 컴포넌트에 스트리밍을 적용하기 위해서는 Suspense 객체를 이용하는 방식을 사용해야 하는데, 이번 시간에 자세히 알아보도록 하자!

import BookItem from "@/components/book-item";
import { BookData } from "@/types";
import { delay } from "@/util/delay";
import { Suspense } from "react";

async function SearchResult({ q }: { q: string }) {
  await delay(1500);
  const response = await fetch(
    `${process.env.NEXT_PUBLIC_API_SERVER_URL}/book/search?q=${q}`,
    { cache: "force-cache" }
  );

  if (!response.ok) {
    return <div>오류가 발생했습니다.</div>;
  }

  const books: BookData[] = await response.json();

  return (
    <div>
      {books.map((book) => (
        <BookItem key={book.id} {...book} />
      ))}
    </div>
  );
}

export default function Page({
  searchParams,
}: {
  searchParams: {
    q?: string;
  };
}) {
  // SearchResult 컴포넌트를 분리함으로써, Page 컴포넌트는 동기적으로 수행됨.
  // q가 undefined일 수도 있기 때문에 빈 문자열의 유니온 타입으로 넘겨줌.
  // 스트리밍을 위해 Suspense 객체로 감싸주고, fallback 옵션을 통해 대체UI를 설정해줌.
  return (
    <Suspense fallback={<div>Loading ...</div>}>
      <SearchResult q={searchParams.q || ""} />
    </Suspense>
  );
}

먼저 Page 컴포넌트에서 비동기 작업을 다루던 코드들을 SearchResult라는 하위 컴포넌트로 분리하였다. 그럼 자연스럽게 Page 컴포넌트에서는 async 키워드를 제거하게 된다.

Page 컴포넌트에서 SearchResult 컴포넌트를 사용할 때 쿼리 스트링을 넘겨줘야 하는데, 이 때 타입 오류가 발생하게 된다. 이는 쿼리 스트링이 저장되는 변수 q가 undefined 타입일 수도 있기 때문! 따라서 빈 문자열의 유니온 타입으로 넘겨준다.

그리고 드디어 Suspense❗️ 리액트의 Suspense 객체를 호출한 후 SearchResult 객체를 감싸준다. 이 때 fallback 옵션은 대체 UI를 설정하는데 이용되는데, 다른 컴포넌트를 넘길 수도 있다.

⚠️ 이 때 지난 포스트에서 언급한 "주의할 점 4번"은 Suspense 객체를 통한 스트리밍에서도 똑같이 적용된다.
하지만 📁 loading.tsx 파일을 이용한 스트리밍과 다르게, Suspense 객체를 통한 스트리밍에서는 이 문제를 쉽게 해결할 수 있다.

return (
    <Suspense 
      key={searchParams.q || ""}
      fallback={<div>Loading ...</div>}
    >
      <SearchResult q={searchParams.q || ""} />
    </Suspense>
  );

바로 key 옵션 🔑 이다. key 옵션을 설정하면, 설정한 key 값이 변화할 때마다 스트리밍을 적용해서 Loading ...이 보여지게 된다.


Suspense는 하나의 컴포넌트 내에서 여러 개의 비동기 컴포넌트들을 동시에 스트리밍할 수 있다

이번엔 비동기 컴포넌트를 두 개 렌더링하는 페이지 컴포넌트에서 Suspense를 적용해보았다.

그리고 RecoBooks 컴포넌트는 3초의 딜레이를, AllBooks 컴포넌트는 1.5초의 딜레이를 추가하였다.
적용 후 페이지를 리로딩하면 위와 같이 두 섹션에서 대체 UI가 보여지는 것을 확인할 수 있다. 1.5초가 지나면 '등록된 모든 도서' 파트가 먼저 보여지고, 3초가 지나면 '지금 추천하는 도서' 파트가 보여진다.

⚡️ 스켈레톤 적용하기

스켈레톤 UI란?

  • 뼈대 역할을 하는 UI.
  • 로딩 시간 동안 사용자는 컨텐츠 로딩 종료 후 나타날 컨텐츠의 모습을 예상할 수 있기 때문에, 훨씬 더 좋은 UX를 제공할 수 있다.

먼저 스켈레톤은 위와 같이 구현할 수 있다. 스켈레톤을 적용할 페이지의 형태에 맞게 뼈대를 구현한 후, css를 적용해준다.

Suspense의 fallback 옵션에 해당 컴포넌트를 넣어주면, 위 이미지처럼 api 로딩 중에 나타나는 스켈레톤을 확인할 수 있다.
스켈레톤과 함께 애니메이션이 적용되는 것도 원한다면, 위 라이브러리를 사용할 수 있다.

Reference

👩🏻‍🏫 한입 크기로 잘라 먹는 Next.js(15+) https://www.inflearn.com/course/한입-크기-nextjs/

profile
꾸준하고 성실하게, FE 개발자 역량을 키워보자 !

0개의 댓글