JAVASCRIPT

Nextjs에서 tanstack-query Prefetch를 왜 해야 할까

Neda 2024. 4. 22. 14:20

Nextjs에서 tanstack-query Prefetch를 왜 해야 할까

react에서는 컴포넌트 내에서 서버 상태를 관리하기 위해 tanstack query와 같은 라이브러리를 통해 상태를 관리합니다. prefetchQuery는 서버에서 데이터를 fetching하고 직렬화하여 클라이언트로 보내는 방법입니다. 왜 이러한 방법이 필요한것일까

 

tanstack query

tanstack query(구 react-query)는 리액트의 (클라이언트) 컴포넌트 내에서 서버 상태를 관리하는 유용한 hook을 제공합니다. 일반적인 흐름은 아래 같습니다

  1. 페이지 로드
  2. 컴포넌트 마운트
  3. 대체자 렌더링
  4. 데이터 가져오기
  5. 데이터를 가진 컴포넌트 렌더링

이 흐름에서는  컴포넌트가 초기 렌더링 되고 데이터를 가져오는 동안 로딩 스피너, 스켈레톤 이미지와 같은 대체자를 표시합니다. 그리고 데이터를 다 가져온 후에 제대로 된 컴포넌트를 표시하게 됩니다.

 

prefetch

prefetch와 hydration 방식은 서버에서 페이지 렌더링 시 데이터를 가져오고 직렬화하여 페이지와 함께 클라이언트에 내려보내는 방식입니다.

import { getPost } from "@/app/getPost";
import Post from "@/app/hydration/post";
import {
  dehydrate,
  HydrationBoundary,
  QueryClient,
} from "@tanstack/react-query";


export default async function HydrationPage() {
  const queryClient = new QueryClient();

  await queryClient.prefetchQuery({
    queryKey: ["post"],
    queryFn: getPost,
    staleTime: 1000 * 60 * 5,
  });

  const state = dehydrate(queryClient);

  return (
    <HydrationBoundary state={state}>
        <h1>Hydration Page</h1>
        <Post />
    </HydrationBoundary>
  );
}

 

 

클라이언트 컴포넌트의 렌더링

클라이언트 컴포넌트는 클라이언트 측에서만 렌더링 될 것 같지만, Nextjs에서는 서버에서도 렌더링이 가능합니다

1. 전체 페이지 로드 시

전체 페이지 로드는 사이트의 페이지를 처음 방문하거나 새로고침을 통해 전체 페이지를 요청하는 경우입니다.

prefetch를 사용하면 서버의 페이지 렌더링 단계에서 데이터를 fetching하고 클라이언트 컴포넌트의 query data로 전달됩니다. 덕분에 클라이언트 컴포넌트지만 초기 html을 서버에서 렌더링할 수 있습니다

prefetch를 사용하지 않으면 서버에서는 아직 data가 없으므로 데이터가 없을 때는 아래와 같이 지정해 둔 fallback을 렌더링합니다

if(isLoading) return <p>loading...</p>

 

2. 후속 탐색 시

후속 탐색은 클라이언트 측에서 Link를 통한 페이지 이동과 같은 탐색을 말합니다

이 때 클라이언트 구성 요소는 서버로 부터 자바스크립트 번들로 다운로드됩니다. 서버에서 렌더링된 HTML 없이 클라이언트에서 전적으로 렌더링 됩니다.

https://nextjs.org/docs/app/building-your-application/rendering/client-components

 

Rendering: Client Components | Next.js

Learn how to use Client Components to render parts of your application on the client.

nextjs.org

 

prefetch와 HydrationBoundary을 사용하면 서버에서 데이터를 fetching하고 클라이언트의 queryclient에 바로 전달할 수 있어 초기 렌더링 시 깜박임이 생기지 않습니다.

클라이언트 컴포넌트에서는 서버에서 Hydration을 했는지 여부를 알지 못해도 됩니다. 그냥  useQuery와 같은 훅을 사용하면 됩니다

Hydration 대신 props를 통해 initialdata를 컴포넌트로 전달할 수도 있습니다. 하지만 tanstack query는 지속적인 데이터의 상태 관리가 필요하기 때문에 사용하는 경우가 많습니다. 따라서 tanstack query를 사용하는 해당 컴포넌트는  props를 통해 초기 상태를 전달 받더라도 클라이언트 컴포넌트로서 훅을 통해 지속적으로 상태를 관리를 해야 합니다. 

또한 데이터를 페칭하는 위치와 이를 사용하는 컴포넌트 간의 계층 거리가 멀게 되면 데이터를 필요한 위치까지 계속 내려줘야 하는 props drilling가 발생할 수 있습니다. HydrationBoundary를 통해 queryClient로 바로 전달하면 전역 상태를 관리하기 때문에 이와 같은 문제가 발생하지 않습니다.

 

TanStack Query Docs

Does this replace [Redux, MobX, etc]? react

tanstack.com

 

Nextjs Caching

nextjs에서는 서버로 들어오는 요청과 배포 전반에 걸쳐 데이터 fetching의 결과값을 유지할 수 있도록 해주는 캐싱 기능이 있습니다. 따라서 아래와 같이 generateMetadata와 prefetch에서 각각 getPost를 호출하여도 실제 데이터 요청은 1번만 가게 됩니다. prefetch를 하지 않을 때는 서버에서 generateMetadata를 위해 1번, 클라이언트에서 query fetch를 위해 1번이 각각 요청되어 총 2번의 데이터 요청이 발생합니다.

export interface Post {
  id: number;
  title: string;
  content: string;
  time: string;
}

export const getPost = async () => {
  const res = await fetch("http://localhost:4000/api/posts/3", {
    next: {
      revalidate: 1,
    },
  });
  
  const data = (await res.json()) as Post;
  return data;
};
import { getPost } from "@/app/getPost";
import Post from "@/app/hydration/post";
import {
  dehydrate,
  HydrationBoundary,
  QueryClient,
} from "@tanstack/react-query";
import { Metadata } from "next";

export async function generateMetadata(): Promise<Metadata> {
  const post = await getPost();

  return {
    title: post.title,
    description: post.content,
  };
}

export default async function HydrationPage() {
  const queryClient = new QueryClient();

  await queryClient.prefetchQuery({
    queryKey: ["post"],
    queryFn: getPost,
  });

  const state = dehydrate(queryClient);

  return (
    <HydrationBoundary state={state}>
        <Post />
    </HydrationBoundary>
  );
}