개발자
류준열

prefetchQuery 이용해서 로딩제거

테이블 컴포넌트의 로딩에 대해 고민하다가 한 작업이다.

Table 데이터를 첫 렌더링때는 서버컴포넌트에서 받은 데이터를 보여주고, 그 이후 인터렉션이 발생하면 클라이언트에서 fetch를 할 수 있지 않을까? 라는 생각을 했다.

두가지가 맘에 걸렸다.

  • 페이지를 넘겼을때 서버 컴포넌트에서 받아온 데이터를 어떻게 클라이언트 사이드 데이터로 교체 할 것인가
  • next 서버에서 데이터를 받아올때 revalidateTime은 어떻게 설정해야 하는가

고민하던 중 tanstack query의 advanced serverside rendering을 보고 해답을 찾았다.

서버 컴포넌트에서 prefetchQuery를 사용해 데이터를 미리 캐싱한 후, HydrationBoundary를 활용해 클라이언트에서 즉시 데이터를 사용할 수 있도록 했다.

서버 컴포넌트에서 데이터 캐싱하기

import { dehydrate, QueryClient } from '@tanstack/react-query';
import { getDevices } from '@/api/devices';

export default async function Page() {
  const queryClient = getQueryClient();
 
  // ['devices',1,'']에 getDevices의 데이터가 캐싱된다.
  // await 걸면 렌더링전에 기다려서 HTML 내려주기까지 시간 더 쓴다.
  // 나는 1초 미만이라 그냥 await 걸었다.
  await queryClient.prefetchQuery({
    queryKey: ['devices',1,''],
    queryFn: getDevices,
  });

  return (
    <HydrationBoundary state={dehydrate(queryClient)}>
		  {children}
    </HydrationBoundary>
  );
}

SSR에서 api 대기 시간 관리

여기서 아래와 같이 Suspense를 이용하면 prefetch는 하지만 로딩이 걸린다.

import { dehydrate, QueryClient } from '@tanstack/react-query';
import { getDevices } from '@/api/devices';

export default async function Page() {
  const queryClient = getQueryClient();
 
  // ['devices',1,'']에 getDevices의 데이터가 캐싱된다.
  // await 제거
  queryClient.prefetchQuery({
    queryKey: ['devices',1,''],
    queryFn: getDevices,
  });

  return (
    <HydrationBoundary state={dehydrate(queryClient)}>
      <Suspense fallback={<div>로딩</div>}>
        {children}
      </Suspense>
    </HydrationBoundary>
  );
}

나는 1초 미만의 api 요청에 로딩을 보여주는게 더 지저분하다고 생각해서 그냥 await 걸고 기다리게 했다.

어쨌든 서버컴포넌트에서 prefetchQuery를 하게 되면 렌더링전에 데이터를 받아서 캐싱하므로, 클라이언트에서는 거의 즉시 렌더링이라고 봐도 된다.