React-Router v7 Framework mode loader와 Suspense 활용 (feat. React 19)

kyle·2025년 4월 22일
0

React

목록 보기
2/2

React-Router v7 Framework mode에서 loader와 Suspense를 활용한 스트리밍 처리 방법을 알아보겠습니다.

목차는 아래와 같습니다.

  1. loader + Suspense + Await를 활용한 스트리밍
  2. loader + Suspense + use API를 활용한 스트리밍
  3. SEO 최적화를 위해 신경 쓸 점

loader + Suspense + Await를 활용한 스트리밍

export async function loader({ params }: Route.LoaderArgs) {
  const city = new Promise<{ city: string }>((resolve) => setTimeout(() => resolve({ city: params.city }), 1000));
  const description = await new Promise<{ description: string }>((resolve) =>
    setTimeout(() => resolve({ description: "one of popular cities in the world" }), 300)
  );
  return { city, description };
}

export default function City({ loaderData }: Route.ComponentProps) {
  const { city, description } = loaderData;
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <Await resolve={city}>{(data) => <div>City: {data.city}</div>}</Await>
      </Suspense>
      <div>Description: {description.description}</div>
    </div>
  );
}

위 코드의 결과로, description만이 바로 보여지게 되며, city만이 스트리밍됩니다.
Suspense의 자식으로 Await 컴포넌트가 사용되고 있는데요, 그 이유는 pending중인 promise를 throw 하기 위함입니다. 기본적으로 Suspense는 throw된 promise를 받고 지연 상태임을 확인하기 때문에 이 예제에서는 Await 없이는 스트리밍이 동작하지않습니다.
promise가 fullfilled 상태가 되면, fallback UI가 Await 컴포넌트의 자식으로 대체됩니다.

loader + Suspense + use API를 활용한 스트리밍

export async function loader({ params }: Route.LoaderArgs) {
  const city = new Promise<{ city: string }>((resolve) => setTimeout(() => resolve({ city: params.city }), 1000));
  const description = await new Promise<{ description: string }>((resolve) =>
    setTimeout(() => resolve({ description: "one of popular cities in the world" }), 300)
  );
  return { city, description };
}

export default function City({ loaderData }: Route.ComponentProps) {
  const { city, description } = loaderData;
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <CityName p={city} />
      </Suspense>
      <div>Description: {description.description}</div>
    </div>
  );
}

const CityName = ({ p }: { p: Promise<{ city: string }> }) => {
  const data = use(p);
  return <div>City Name: {data.city}</div>;
};

use는 Canary에서 실험적으로 사용되던 API였는데요. React 19에서 공식적으로 사용할 수 있게 되었습니다. 사용방법은 크게 차이가 없습니다. 클라이언트 컴포넌트를 생성하고, promise를 prop으로 받은 뒤, use()로 wrap 하면 됩니다. use가 알아서 제일 가까운 Suspense Boundary를 찾아 promise를 throw 해줍니다.

SEO 최적화를 위해 신경 쓸 점

Suspense 사용시 서버에서는 fallback UI를 렌더링하도록 설계됐습니다.

네트워크에서 직접 까봤을 때, 스트리밍을 적용한 부분이 Loading...으로 표기되고 있습니다.

따라서 SSR을 사용할 때, SEO 최적화 관점에서 상대적으로 덜 중요한 데이터에 스트리밍을 적용하고, 중요한 데이터엔 스트리밍을 적용하지않는 것이 중요합니다.

profile
DX를 사랑하는 개발자

0개의 댓글