[Next.js/Tanstack-Query] Next에서 prefetch하기

Next.js에서 prefetch의 필요성

Next.js는 동적 데이터도 빌드 시점에 미리 fetch해서 로딩 속도를 향상시킬 수 있다. (SSG)

하지만 빌드 시점의 상태가 아닌 최신 상태를 받아야하면서도, 로딩 속도를 향상시키고 싶다면 prefetch 를 활용할 수 있다!

 

json-server 옵션으로 user 목록을 json-server에서 로드해오는 fetch에 2초 딜레이를 준 상태다

메인 페이지에서 사용자 목록을 prefetch 해두었기 때문에 other 페이지에서 사용자 목록을 로딩 없이 바로 확인할 수 있다!

예제

Query Provider 만들기

하위 컴포넌트들이 queryClient에 접근할 수 있도록 Query Provider를 생성한다

// src/components/providers/TQProvider.tsx
"use client";

import {
  isServer,
  QueryClient,
  QueryClientProvider,
} from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";

function makeQueryClient() {
  return new QueryClient({
    defaultOptions: {
      queries: {
        staleTime: 60 * 1000,
      },
    },
  });
}

let browserQueryClient: QueryClient | undefined = undefined;

function getQueryClient() {
  if (isServer) {
    // Server: always make a new query client
    return makeQueryClient();
  } else {
    // Browser: make a new query client if we don't already have one
    // This is very important, so we don't re-make a new client if React
    // suspends during the initial render. This may not be needed if we
    // have a suspense boundary BELOW the creation of the query client
    if (!browserQueryClient) browserQueryClient = makeQueryClient();
    return browserQueryClient;
  }
}

export default function Providers({ children }: { children: React.ReactNode }) {
  // NOTE: Avoid useState when initializing the query client if you don't
  //       have a suspense boundary between this and the code that may
  //       suspend because React will throw away the client on the initial
  //       render if it suspends and there is no boundary
  const queryClient = getQueryClient();

  return (
    <QueryClientProvider client={queryClient}>
      {children}
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  );
}
// app/layout.tsx

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}

 

prefetch 하기

prefetch를 수행할 queryClient를 생성하고, queryClient.prefetchQuery를 호출한다.

<HydrationBoundary/>에 state에 dehydrate(queryClient)를 넘겨 queryClient에 캐싱된 값을 직렬화해 넘겨준다. (json 형태)

(HydrationBoundary 하위의 요소들 외에도 다른 queryClient가 캐싱된 값을 공유하게되어, 다른 queryClient에서도 해당 값을 사용할 수 있게 된다.)

// app/page.tsx

import {
  dehydrate,
  HydrationBoundary,
  QueryClient,
} from "@tanstack/react-query";
import Link from "next/link";

export default async function Home() {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: {
        staleTime: 3000,
      },
    },
  });

  const fetchUsers = async () => {
    const response = await fetch("http://localhost:4000/users");
    const data = await response.json();
    return data;
  };

  await queryClient.prefetchQuery({
    queryKey: ["users"],
    queryFn: () => fetchUsers(),
  });

  return (
    <div className="flex flex-col gap-3 justify-center items-center min-h-screen font-[family-name:var(--font-geist-sans)]">
      <div className="flex gap-3">
        <button className="shadow shadow-gray-500/50 rounded p-2">
          Increment
        </button>
        <button className="shadow shadow-gray-500/50 rounded p-2">
          Decrement
        </button>
      </div>
      <HydrationBoundary state={dehydrate(queryClient)}>
        <Link href="/other">Other 페이지로 이동하기</Link>
      </HydrationBoundary>
    </div>
  );
}

 

prefetch된 값 사용하기

다른 페이지에서 prefetch와 동일한 queryKey의 값을 사용하면 이미 캐싱되어있는 데이터를 바로 사용할 수 있다!

"use client";
import { User } from "@/query/useUser";
import { useQuery } from "@tanstack/react-query";

const Page = () => {
  const { data, isLoading } = useQuery({
    queryKey: ["users"],
    queryFn: async () => {
      const response = await fetch("http://localhost:4000/users");
      const data: User[] = await response.json();
      return data;
    },
  });

  if (isLoading) return <div>Loading...</div>;

  return (
    <div className="w-full min-h-screen flex flex-col justify-center items-center">
      <h1 className="font-bold text-xl">other 페이지 입니다</h1>
      {data?.map((user) => {
        return <div key={user.id}>{user.name}</div>;
      })}
    </div>
  );
};

export default Page;

 

 

성공!

'Next.js' 카테고리의 다른 글

[Next.js] Auth와 Middleware  (0) 2024.09.30
[Next.js] Parallel Routes & Intercepting Routes  (1) 2024.09.29
[Next.js] Caching  (1) 2024.09.28
[Next.js] Router Handler & Server Action  (0) 2024.09.27
[Next.js] Asset 최적화  (1) 2024.09.27