Project

React Query์™€ SSR: ์„œ๋ฒ„ ์‚ฌ์ด๋“œ ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ์™€ Hydration ํ™œ์šฉ

๐Ÿถzio 2024. 12. 7. 17:33

 

ํ˜„์žฌ ํ”„๋กœ์ ํŠธ๋Š” ์„œ๋ฒ„ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง(SSR)๊ณผ ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง(CSR)์ด ํ˜ผํ•ฉ๋œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด๋‹ค.

  • ์ •์ ์ธ ํŽ˜์ด์ง€: ๋ฐ์ดํ„ฐ ๋ณ€ํ™”๊ฐ€ ์ ์–ด ์ •์  ์ฝ˜ํ…์ธ ๋กœ ์ œ๊ณต (e.g. ๋ฉ”์ธ ํ™”๋ฉด)
  • ๋™์ ์ธ ํŽ˜์ด์ง€: ๋ฐ์ดํ„ฐ ๋ณ€ํ™”๊ฐ€ ๋งŽ์•„ ์—…๋ฐ์ดํŠธ๊ฐ€ ์žฆ์Œ (e.g. ๊ฒŒ์‹œ๋ฌผ ๋ฆฌ์ŠคํŠธ)

 

์ด๋Ÿฌํ•œ ์ƒํ™ฉ์—์„œ, ๋‚˜๋Š” ์ •์ ์ธ ํŽ˜์ด์ง€๋Š” Next.js์˜ ์บ์‹œ ๊ธฐ๋Šฅ, ๋™์ ์ธ ํŽ˜์ด์ง€๋Š” React Query๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ข‹์„ ๊ฒƒ์ด๋ผ ์ƒ๊ฐํ–ˆ์—ˆ๋‹ค.

 

ํ•˜์ง€๋งŒ ๊ด€๋ฆฌ์˜ ๋ณต์žก์„ฑ์„ ์ค„์ด๊ธฐ ์œ„ํ•ด ํ•œ ๊ฐ€์ง€ ๋ฐฉ์‹์œผ๋กœ ํ†ต์ผํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค๋Š” ํ”ผ๋“œ๋ฐฑ์„ ๋ฐ›์•˜๋‹ค.

๊ทธ๋ž˜์„œ React Query๋ฅผ SSR ํ™˜๊ฒฝ์—์„œ๋„ ํ™œ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๊ณ ๋ฏผํ–ˆ๋‹ค.

 

 

 


 

 

React Query

React Query์˜ useQuery๋Š” ํด๋ผ์ด์–ธํŠธ ์ธก ๋ฐ์ดํ„ฐ ํŒจ์นญ์„ ๋„์™€์ค€๋‹ค.

SSR์—์„œ๋Š” ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„ ์™„์„ฑ๋œ HTML์„ ๋ณด๋‚ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์— React Query์˜ ์บ์‹œ๋ฅผ ํด๋ผ์ด์–ธํŠธ์—์„œ ๋ฐ”๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค.

์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฏธ๋ฆฌ ํŒจ์นญํ•˜๊ณ , ํด๋ผ์ด์–ธํŠธ์—์„œ React Query ์บ์‹œ๋ฅผ ๋ณต์›ํ•˜๋Š” ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•œ๋‹ค.

 

 

 

SSR์—์„œ React Query

1. dehydrate

์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฏธ๋ฆฌ ํŒจ์นญํ•˜๊ณ , ์ด๋ฅผ ์ง๋ ฌํ™”ํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ๋กœ ์ „๋‹ฌํ•˜๋Š” ์—ญํ• ์„ ํ•œ๋‹ค.

 

2. HydrationBoundary

ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ์ „๋‹ฌ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ React Query ์บ์‹œ์— ๋ณต์›(hydration)ํ•˜๋Š” ์—ญํ• ์„ ํ•œ๋‹ค.

 

 

 

๋™์ž‘ ๊ณผ์ •

1. ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ ํŒจ์นญ

React Query์˜ `prefetchQuery`๋ฅผ ์‚ฌ์šฉํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ์„œ๋ฒ„์—์„œ ๋ฏธ๋ฆฌ ๊ฐ€์ ธ์˜จ๋‹ค.

 

2. ํด๋ผ์ด์–ธํŠธ๋กœ ๋ฐ์ดํ„ฐ ์ „์†ก

์„œ๋ฒ„์—์„œ ์ง๋ ฌํ™”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ `dehydrate`๋ฅผ ํ†ตํ•ด ํด๋ผ์ด์–ธํŠธ๋กœ ์ „๋‹ฌํ•œ๋‹ค.

 

3. ํด๋ผ์ด์–ธํŠธ์—์„œ ๋ฐ์ดํ„ฐ ํ•˜์ด๋“œ๋ ˆ์ด์…˜

ํด๋ผ์ด์–ธํŠธ๋Š” `HydrationBoundary`๋ฅผ ํ†ตํ•ด ์ „๋‹ฌ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ React Query ์บ์‹œ์— ์ €์žฅํ•˜๊ณ  ์žฌ์‚ฌ์šฉํ•œ๋‹ค.

 

 

 


์˜ˆ์ œ ์ฝ”๋“œ

0. Provider ์„ค์ •

// queryProvider.ts

'use client';

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

function makeQueryClient() {
  return new QueryClient({
    defaultOptions: {
      queries: {
        // ์ž„์‹œ ์„ค์ •
        staleTime: 60 * 1000,
        refetchInterval: 60 * 1000,
      },
    },
  });
}

let browserQueryClient: QueryClient | undefined;

function getQueryClient() {
  if (isServer) return makeQueryClient();
  if (!browserQueryClient) browserQueryClient = makeQueryClient();
  return browserQueryClient;
}

function QueryProviders({ children }: PropsWithChildren) {
  const queryClient = getQueryClient();
  return (
    <QueryClientProvider client={queryClient}>
      <ReactQueryDevtools initialIsOpen={false} client={queryClient} />
      {children}
    </QueryClientProvider>
  );
}

export default QueryProviders;

makeQueryClient

์—ญํ• : ๋ฆฌ์•กํŠธ ์ฟผ๋ฆฌ์˜ QueryClient ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.

  • defaultOptions์„ ์‚ฌ์šฉํ•ด ์ฟผ๋ฆฌ ๊ธฐ๋ณธ ๋™์ž‘(์บ์‹ฑ, ๊ฐฑ์‹  ๋“ฑ)์„ ์ •์˜ํ•œ๋‹ค.

 

getQueryClient

์—ญํ• : ์„œ๋ฒ„์™€ ๋ธŒ๋ผ์šฐ์ € ํ™˜๊ฒฝ์— ๋งž๊ฒŒ QueryClient๋ฅผ ๊ด€๋ฆฌํ•œ๋‹ค.

  • ์„œ๋ฒ„ ํ™˜๊ฒฝ(isServer๊ฐ€ true)์ผ ๋•Œ, ํ•ญ์ƒ ์ƒˆ๋กœ์šด QueryClient๋ฅผ ์ƒ์„ฑํ•œ๋‹ค. (์„œ๋ฒ„์—์„œ๋Š” ์š”์ฒญ ๊ฐ„ ์ƒํƒœ๋ฅผ ๊ณต์œ ํ•˜๋ฉด ์•ˆ ๋˜๊ธฐ ๋•Œ๋ฌธ)
  • ๋ธŒ๋ผ์šฐ์ € ํ™˜๊ฒฝ(isServer๊ฐ€ false)์ผ ๋•Œ, ๋ธŒ๋ผ์šฐ์ €์—์„œ๋Š” browserQueryClient๋ผ๋Š” ์ „์—ญ ๋ณ€์ˆ˜์— ์ €์žฅํ•˜์—ฌ ์žฌ์‚ฌ์šฉํ•œ๋‹ค. ๋™์ผํ•œ QueryClient๋ฅผ ์žฌ์‚ฌ์šฉํ•ด ์บ์‹œ์™€ ์ƒํƒœ๋ฅผ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

QueryProvisers

์—ญํ• : getQueryClient๋กœ ์„ค์ •ํ•œ QueryClient๊ฐ์ฒด๋ฅผ Provider๋ฅผ ํ†ตํ•ด ์ „์—ญ์ ์œผ๋กœ ์ฟผ๋ฆฌ ์ƒํƒœ๋ฅผ ๊ณต์œ ํ•œ๋‹ค.

 

 

// layout.tsx

import QueryProviders from '@/utils/queryProvider';

const RootLayout = async ({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) => {
  return (
    <html lang="ko">
      <body>
        <QueryProviders>
          {children}
        </QueryProviders>
      </body>
    </html>
  );
};

export default RootLayout;

 

 

1. ๋ฐ์ดํ„ฐ ํŒจ์นญ ํ•จ์ˆ˜

jsonplaceholder๋ฅผ ์ด์šฉํ•œ ๋”๋ฏธ ๋ฐ์ดํ„ฐ ํ™œ์šฉ

export const fetchPosts = async (pageNum = 1) => {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/posts?_limit=10&_page=${pageNum}`,
  );
  return response.json();
};

 

 

2. SSR์—์„œ์˜ ๋ฐ์ดํ„ฐ ํŒจ์นญ๊ณผ Hydration

// page.tsx

import {
  dehydrate,
  HydrationBoundary,
  QueryClient,
} from '@tanstack/react-query';
import Posts from './Post';

const page = async () => {
  const queryClient = new QueryClient();

  // ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ ํŒจ์นญ
  await queryClient.prefetchQuery({
    queryKey: ['posts'],
    queryFn: () => fetchPosts(),
  });

  return (
    <HydrationBoundary state={dehydrate(queryClient)}>
      <h1>์„œ๋ฒ„ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง ํ…Œ์ŠคํŠธ</h1>
      <div>----------------------</div>
      <Posts />
    </HydrationBoundary>
  );
};

export default page;
  • HydrationBoundary : ์„œ๋ฒ„์—์„œ ๋ฏธ๋ฆฌ ํŒจ์นญํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ํด๋ผ์ด์–ธํŠธ์—์„œ ๋ณต์›ํ•˜์—ฌ React Query ์บ์‹œ๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•œ๋‹ค.

 

 

3. ํด๋ผ์ด์–ธํŠธ์—์„œ ๋ฐ์ดํ„ฐ ํ™œ์šฉ

Posts ์ปดํฌ๋„ŒํŠธ๋Š” React Query์˜ useQuery๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ํด๋ผ์ด์–ธํŠธ์—์„œ ๋ถˆ๋Ÿฌ์˜จ๋‹ค. ์ด๋ฏธ ํ•˜์ด๋“œ๋ ˆ์ด์…˜๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์žฌ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜, ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์„ ๊ฒฝ์šฐ ์ƒˆ๋กœ ํŒจ์นญํ•œ๋‹ค.

// Posts.tsx

'use client';

import { useQuery } from '@tanstack/react-query';
import { fetchPosts } from './page';

const Posts = () => {
  console.log('Posts ์ปดํฌ๋„ŒํŠธ');
  const { data } = useQuery({
    queryKey: ['posts'],
    queryFn: () => fetchPosts(),
  });
  return (
    <div>
      {data?.map((post) => (
        <div key={post.id}>
          <h3>{post.title}</h3>
          <p>{post.body}</p>
        </div>
      ))}
    </div>
  );
};

export default Posts;

 

 

 

React Query์™€ SSR์˜ ์ด์ 

  • ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฏธ๋ฆฌ ๊ฐ€์ ธ์˜ค๊ธฐ: ํŽ˜์ด์ง€ ๋กœ๋”ฉ ์†๋„๊ฐ€ ๋นจ๋ผ์ง€๊ณ  SEO ์ตœ์ ํ™”๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.
  • ํด๋ผ์ด์–ธํŠธ์—์„œ ๋ฐ์ดํ„ฐ ๋ณต์›: ์ด๋ฏธ ๊ฐ€์ ธ์˜จ ๋ฐ์ดํ„ฐ๋ฅผ ์žฌ์‚ฌ์šฉํ•˜์—ฌ ๋ถˆํ•„์š”ํ•œ ์ถ”๊ฐ€ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์„ ์ค„์ธ๋‹ค.
  • ์œ ์—ฐํ•œ ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ: ์ •์ /๋™์  ํŽ˜์ด์ง€๋ฅผ ๋ชจ๋‘ ํšจ๊ณผ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

์ฐธ๊ณ ๋กœ SSR๋กœ ๋™์ž‘ํ•˜๋Š”์ง€ ํ…Œ์ŠคํŠธ ํ•ด๋ณด๊ณ  ์‹ถ๋‹ค๋ฉด ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋„๊ณ  ๋ Œ๋”๋งํ•˜๋ฉด ๋œ๋‹ค.

 


 

 

 

Next.js์˜ ๋‚ด์žฅ ์บ์‹ฑ ๊ธฐ๋Šฅ๊ณผ fetch์—์„œ์˜ ์กฐ๊ฑด๋ถ€ ์บ์‹ฑ(Conditional Request)์„ ํ†ตํ•ด ๋ฐ์ดํ„ฐ์˜ stale ์ƒํƒœ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜๋ฉด์„œ, ‘React Query๋ฅผ ๊ณ„์† ์‚ฌ์šฉํ•ด์•ผ ํ• ๊นŒ?’๋ผ๋Š” ์˜๋ฌธ์ด ๋“ค์—ˆ๋‹ค.

 

React Query๊ฐ€ ์ œ๊ณตํ•˜๋Š” ์บ์‹ฑ, ์—๋Ÿฌ ํ•ธ๋“ค๋ง, ๋ฌดํ•œ ์Šคํฌ๋กค ๋“ฑ ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ์ด ๋งค๋ ฅ์ ์ด์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋งŽ์ด ์‚ฌ์šฉ๋˜์—ˆ๋‹ค. ์ง€๊ธˆ๋„ React๋กœ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•œ๋‹ค๋ฉด React Query๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ ๊ฐ™๋‹ค. ํ•˜์ง€๋งŒ Next.js์™€ ๋ธŒ๋ผ์šฐ์ € fetch API์˜ ๊ธฐ๋Šฅ๋“ค์ด ์ ์  ๋” ๋ฐœ์ „ํ•˜๋ฉด์„œ, ๋™์ผํ•œ ๊ธฐ๋Šฅ์„ ๋” ๊ฐ„๋‹จํ•˜๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋Š” ์˜ต์…˜๋“ค์ด ๋Š˜์–ด๋‚˜๊ณ  ์žˆ๋‹ค.

 

React Query์˜ ๊ธฐ๋Šฅ์€ ์—ฌ์ „ํžˆ ๊ฐ•๋ ฅํ•˜๊ณ  ์œ ์šฉํ•˜์ง€๋งŒ, ์•ž์œผ๋กœ๋Š” ์—ฌ๋Ÿฌ๊ฐ€์ง€๋ฅผ ๊ณ ๋ คํ•ด์•ผ ํ•  ๊ฒƒ ๊ฐ™๋‹ค.

 

 

 

์ฐธ๊ณ  ์ž๋ฃŒ

https://soobing.github.io/react/server-rendering-and-react-query/

 

์„œ๋ฒ„์—์„œ React Query prefetching ํ•œ ๋ฐ์ดํ„ฐ ์‚ฌ์šฉํ•˜๊ธฐ

Next.js๋‚˜ Remix ๊ฐ™์€ ํ”„๋ ˆ์ž„์›Œํฌ ๋‚ด์—์„œ React-Query๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, ์„œ๋ฒ„ ๋ Œ๋”๋ง ๋  ๋•Œ ์š”์ฒญ ํ›„ ์‘๋‹ต๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ SPA ๋ฐฉ์‹์œผ๋กœ ์ „ํ™˜๋˜๊ณ  ๋‚˜์„œ๋„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์„๊นŒ์š”? ์–ด๋–ป๊ฒŒ ๊ฐ€๋Šฅํ• ๊นŒ์š”? React Query์˜ hy

soobing.github.io

 

https://sonblog.vercel.app/blogs/blog/nextjs/react-query-ssr

 

React Query SSR

react query์—์„œ ssr์„ ์ ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

sonblog.vercel.app

 

https://tanstack.com/query/v5/docs/framework/react/guides/advanced-ssr

 

Advanced Server Rendering | TanStack Query React Docs

Welcome to the Advanced Server Rendering guide, where you will learn all about using React Query with streaming, Server Components and the Next.js app router. You might want to read the before this on...

tanstack.com