๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

ํ”„๋ก ํŠธ์—”๋“œ/React

Next.js 12 ์—์„œ React Query v4 + SSR ํ™œ์šฉํ•˜๊ธฐ

728x90

 

๋ณธ ๋ฌธ์„œ์—์„œ๋Š”, Next.js 12์—์„œ React-query ๋ฅผ ์ ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณผ ๊ฒƒ์ด๋‹ค. 

ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ์—์„œ ์‚ฌ์šฉํ•˜๋Š”์ง€, ์„œ๋ฒ„ ์‚ฌ์ด๋“œ์—์„œ ์‚ฌ์šฉํ•˜๋Š”์ง€์— ๋”ฐ๋ผ์„œ ๊ตฌ์ฒด์ ์ธ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•๊ณผ ๋™์ž‘ ๋ฐฉ์‹์— ์ฐจ์ด๊ฐ€ ์žˆ๋‹ค.

 

https://tanstack.com/query/v4/docs/framework/react/guides/ssr

 

๐Ÿ”น 1. ํด๋ผ์ด์–ธํŠธ์—์„œ๋งŒ React Query ์‚ฌ์šฉ (๊ธฐ๋ณธ)

// pages/users.tsx
import { useQuery } from '@tanstack/react-query';

export default function Users() {
  const { data, isLoading } = useQuery(['users'], fetchUsers);
  ...
}
 
  • useQuery๋Š” ํด๋ผ์ด์–ธํŠธ์—์„œ ์ž‘๋™ → CSR ๊ธฐ๋ฐ˜
  • ์„œ๋ฒ„๋Š” ๋‹จ์ˆœํžˆ HTML ์…ธ์„ ๋ Œ๋”ํ•˜๊ณ , ๋ฐ์ดํ„ฐ๋Š” ํด๋ผ์ด์–ธํŠธ์—์„œ fetch

โžก๏ธ ์žฅ์ : UX ๋น ๋ฆ„, ์บ์‹œ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅ
โžก๏ธ ๋‹จ์ : SEO ๋ฐ ์ดˆ๊ธฐ LCP ์†๋„ ํ•œ๊ณ„

 

๐Ÿ”น 2. SSR/SSG + React Query ์‚ฌ์šฉ (Hydration ๋ฐฉ์‹)

SSR์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฏธ๋ฆฌ fetch → React Query๋กœ Hydrateํ•˜๋Š” ๋ฐฉ์‹

// pages/users.tsx
import { QueryClient, dehydrate } from '@tanstack/react-query';

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

  await queryClient.prefetchQuery(['users'], fetchUsers);

  return {
    props: {
      dehydratedState: dehydrate(queryClient),
    },
  };
}

 

<Hydrate state={pageProps.dehydratedState}>
  <QueryComponent />
</Hydrate>

 

 

โžก๏ธ ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฏธ๋ฆฌ ๋ถˆ๋Ÿฌ์˜ค๊ณ  HTML์— ํฌํ•จ → SEO, LCP, FCP ์„ฑ๋Šฅ ํ–ฅ์ƒ
โžก๏ธ ํด๋ผ์ด์–ธํŠธ์—์„œ๋Š” ์ด๋ฏธ ์บ์‹œ๋œ ๋ฐ์ดํ„ฐ๋กœ ์ฆ‰์‹œ ๋ Œ๋”๋ง

 

 

๋‹จ์ˆœ SSR๋งŒ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๊ณผ SSR/SSG + React Query ๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ ์‚ฌ์ด์— ๋ฌด์Šจ ์ฐจ์ด๊ฐ€ ์žˆ๋Š”์ง€ ๊ถ๊ธˆํ•  ์ˆ˜ ์žˆ๋‹ค. 

 

1. ๋‹จ์ˆœ SSR๋งŒ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ

  • ์„œ๋ฒ„์—์„œ getServerSideProps ๊ฐ™์€ ํ•จ์ˆ˜๋กœ ๋ฐ์ดํ„ฐ๋ฅผ fetchํ•˜๊ณ , HTML์— ํฌํ•จ์‹œ์ผœ์„œ ํด๋ผ์ด์–ธํŠธ๋กœ ๋ณด๋ƒ„
  • ํด๋ผ์ด์–ธํŠธ๋Š” ์ด๋ฏธ ๋ฐ์ดํ„ฐ๊ฐ€ ํฌํ•จ๋œ HTML์„ ๋ฐ›์•„ ๋ Œ๋”๋งํ•˜๋ฏ€๋กœ, ์ดˆ๊ธฐ ๋กœ๋”ฉ์ด ๋น ๋ฆ„
  • ํ•˜์ง€๋งŒ ์ดํ›„, ํด๋ผ์ด์–ธํŠธ ์ชฝ์˜ ์ƒํƒœ ๊ด€๋ฆฌ๋‚˜ ์บ์‹ฑ์€ ๋”ฐ๋กœ ๊ตฌํ˜„ํ•ด์ค˜์•ผ ํ•จ
  • ํŽ˜์ด์ง€ ์ด๋™ ์‹œ์—๋„ ๋‹ค์‹œ SSR์ด ์ผ์–ด๋‚˜๊ธฐ ๋•Œ๋ฌธ์— ๋„คํŠธ์›Œํฌ ๋น„์šฉ์ด ๋ฐ˜๋ณต๋จ

๐Ÿ”ธ ๋‹จ์ : ๋ฐ์ดํ„ฐ ์žฌํ™œ์šฉ ๋ถˆ๊ฐ€, ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ ๊ด€๋ฆฌ ๋ถ€์กฑ, ์ถ”๊ฐ€ API ํ˜ธ์ถœ ๋ฐœ์ƒ ๊ฐ€๋Šฅ์„ฑ

 

2. SSR/SSG + React Query ์‚ฌ์šฉ

  • ์„œ๋ฒ„์—์„œ React Query๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ fetchํ•˜๊ณ , dehydrate()๋กœ ์ดˆ๊ธฐ ์บ์‹œ ์ƒํƒœ๋ฅผ HTML์— ์ฃผ์ž…
  • ํด๋ผ์ด์–ธํŠธ๋Š” hydrate()๋กœ ์บ์‹œ๋œ ๋ฐ์ดํ„ฐ๋กœ ๋ฐ”๋กœ ๋ Œ๋”๋ง, ์ถ”๊ฐ€ API ์š”์ฒญ ์—†์ด๋„ ๋ฐ”๋กœ ์ธํ„ฐ๋ž™์…˜ ๊ฐ€๋Šฅ
  • ํŽ˜์ด์ง€ ์ด๋™ ์‹œ์—๋„ React Query ์บ์‹œ๋ฅผ ์žฌํ™œ์šฉ, ๋ถˆํ•„์š”ํ•œ refetch ๋ฐฉ์ง€
  • staleTime, cacheTime ๋“ฑ์„ ์„ค์ •ํ•ด ๋ฐ์ดํ„ฐ freshness๋„ ์กฐ์ ˆ ๊ฐ€๋Šฅ

โœ… ์žฅ์ : CSR์—์„œ๋„ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ๋™์ž‘, ๋ฐ์ดํ„ฐ ์ค‘๋ณต ์š”์ฒญ ์—†์Œ, ์ดˆ๊ธฐ ๋กœ๋”ฉ ์†๋„ + UX + ์บ์‹ฑ ์ „๋žต ์ตœ์ ํ™”

 

 

SSR/SSG + React Query ๊ตฌํ˜„ ๋ฐฉ์‹

 

https://tanstack.com/query/latest/docs/framework/react/guides/ssr#server-rendering--react-query

 

Server Rendering & Hydration | TanStack Query React Docs

In this guide you'll learn how to use React Query with server rendering. See the guide on for some background. You might also want to check out the before that. For advanced server rendering patterns,...

tanstack.com

 

 

์ด๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ์‹์—๋Š” ํฌ๊ฒŒ ๋‘๊ฐ€์ง€๊ฐ€ ์žˆ๋‹ค.

 

1๏ธโƒฃ initialData ์‚ฌ์šฉ

์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ fetch ํ•œ ๋‹ค์Œ ํ•ด๋‹น props ๋ฅผ useQuery() ํ•จ์ˆ˜์˜ initialData ์˜ต์…˜์œผ๋กœ ์›์‹œ ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ

export async function getServerSideProps() {
  const posts = await getPosts()
  return { props: { posts } }
}

function Posts(props) {
  const { data } = useQuery({
    queryKey: ['posts'],
    queryFn: getPosts,
    initialData: props.posts,
  })

  // ...
}

 

setup์ด ๊ฐ„๋‹จํ•˜์ง€๋งŒ ๋ช‡๊ฐ€์ง€ tradeoff ๊ฐ€ ์žˆ๋‹ค.

 

1. ํ•˜์œ„ ํŠธ๋ฆฌ์—์„œ useQuery ํ˜ธ์ถœ ์‹œ, initialData๋„ ํ•จ๊ป˜ ์ „๋‹ฌํ•ด์•ผ ํ•จ

  • ์ปดํฌ๋„ŒํŠธ ํŠธ๋ฆฌ ๊นŠ์ˆ™ํ•œ ๊ณณ์—์„œ useQuery๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ, ํ•ด๋‹น ์œ„์น˜๊นŒ์ง€ initialData๋ฅผ ๊ณ„์† props๋กœ ๋‚ด๋ ค์ค˜์•ผ ํ•ด์„œ ๋ฒˆ๊ฑฐ๋กญ๊ณ  ๋ณต์žกํ•จ.

2. ์—ฌ๋Ÿฌ ๊ณณ์—์„œ ๋™์ผ ์ฟผ๋ฆฌ๋ฅผ ์“ฐ๋Š” ๊ฒฝ์šฐ, ํ•œ ๊ณณ์—๋งŒ initialData ์ „๋‹ฌํ•˜๋ฉด ์œ„ํ—˜

  • ํ•˜๋‚˜์˜ ์ปดํฌ๋„ŒํŠธ๋งŒ initialData๋ฅผ ์„ค์ •ํ•˜๊ณ  ๋‹ค๋ฅธ ๊ณณ์€ ์„ค์ •ํ•˜์ง€ ์•Š์œผ๋ฉด, ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์‚ฌ๋ผ์งˆ ๋•Œ ๋‚˜๋จธ์ง€ ์ปดํฌ๋„ŒํŠธ๋“ค์ด ๋ฐ์ดํ„ฐ๋ฅผ ๋ชป ๋ฐ›๊ฒŒ ๋  ์ˆ˜ ์žˆ์Œ.
  • ์ฆ‰, ์ปดํฌ๋„ŒํŠธ ์žฌ๋ฐฐ์น˜/๋ฆฌํŒฉํ„ฐ๋ง ์‹œ ์ทจ์•ฝํ•ด์ง.

3. SSR์—์„œ initialData๋Š” ์ •ํ™•ํ•œ fetch ์‹œ์ ์„ ๋ชจ๋ฆ„

  • initialData๋Š” ์–ธ์ œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™”๋Š”์ง€๋ฅผ ๊ธฐ๋กํ•˜์ง€ ์•Š์Œ (dataUpdatedAt ์—†์Œ).
  • ๊ทธ๋ž˜์„œ ํŽ˜์ด์ง€๊ฐ€ ์˜ค๋ž˜ ์—ด๋ ค ์žˆ์–ด๋„ "์–ธ์ œ ๋ฐ์ดํ„ฐ๊ฐ€ ์ƒ์„ฑ๋๋Š”์ง€" ๋ชจ๋ฅด๊ธฐ ๋•Œ๋ฌธ์—, ๋ฆฌํŒจ์นญ ํŒ๋‹จ์ด ์–ด๋ ต๊ณ  ๋ถˆ์•ˆ์ •ํ•จ.

4. ์บ์‹œ์— ์ด๋ฏธ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋‹ค๋ฉด, initialData๋Š” ๋ฎ์–ด์“ฐ๊ธฐ ์•ˆ ๋จ

  • ๋งŒ์•ฝ ์บ์‹œ์— ์ด์ „ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๊ณ , ์ƒˆ๋กœ์šด initialData๊ฐ€ ์‹ค์ œ๋กœ ๋” ์ตœ์‹ ์ด๋”๋ผ๋„ ๊ธฐ์กด ๋ฐ์ดํ„ฐ๋ฅผ ์œ ์ง€ํ•จ.
  • ํŠนํžˆ getServerSideProps ๊ฐ™์€ ๊ฒฝ์šฐ, ํŽ˜์ด์ง€๋ฅผ ์™”๋‹ค๊ฐ”๋‹ค ํ•  ๋•Œ ๋งค๋ฒˆ ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค์ง€๋งŒ, ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ๋กœ๋งŒ ์บ์‹œ๊ฐ€ ์œ ์ง€๋˜๋ฏ€๋กœ ๊ฐฑ์‹ ๋˜์ง€ ์•Š์Œ.

=> ์ฆ‰, ์ •ํ™•ํ•˜๊ณ  ์ผ๊ด€๋œ ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™”์— ์ง€์žฅ์„ ๋ฐ›์Œ

2๏ธโƒฃ Hydration ์‚ฌ์šฉ

์„œ๋ฒ„์—์„œ query๋ฅผ prefetchํ•˜๊ณ , ์บ์‹œ๋ฅผ dehydrateํ•˜๊ณ , client์—์„œ rehydrate ํ•œ๋‹ค. ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„ ๋ชจ๋‘์—์„œ ๋™์ผํ•œ QueryClient ์ƒํƒœ๋กœ ์‹œ์ž‘ํ•˜์—ฌ, SSR๋กœ ์ƒ์„ฑ๋œ HTML๊ณผ ํด๋ผ์ด์–ธํŠธ์˜ ์ดˆ๊ธฐ ๋งˆํฌ์—…์„ ์ผ์น˜์‹œํ‚ค๊ณ , stale ์บ์‹œ ๋ฌธ์ œ๋ฅผ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

๐Ÿ”„ ๋‹จ๊ณ„๋ณ„ ์„ค๋ช…

1. Preloading ๋‹จ๊ณ„ (์„œ๋ฒ„ ์ธก)

  • QueryClient๋ฅผ ์ƒ์„ฑ:
const queryClient = new QueryClient();
  • ํ•„์š”ํ•œ ์ฟผ๋ฆฌ๋ฅผ ๋ฏธ๋ฆฌ prefetch:
await Promise.all([
  queryClient.prefetchQuery(['todos'], fetchTodos),
  queryClient.prefetchQuery(['user', userId], fetchUser),
]);
  • dehydrate(queryClient)๋กœ ์ง๋ ฌํ™”ํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ์— ์ „๋‹ฌ:
    (์˜ˆ: getServerSideProps, loader, getStaticProps ๋“ฑ)
return {
  props: {
    dehydratedState: dehydrate(queryClient),
  },
};

 

๐Ÿ”ง dehydrate()๋ž€?

dehydrate(queryClient)๋Š” React Query์˜ ์บ์‹œ ์ƒํƒœ(queryClient.getQueryCache())๋ฅผ ์ง๋ ฌํ™” ๊ฐ€๋Šฅํ•œ ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.

  • "dehydrate" = ๋ง ๊ทธ๋Œ€๋กœ "์ˆ˜๋ถ„์„ ์ œ๊ฑฐํ•˜๋‹ค" → ํ•„์š”ํ•œ ์ •๋ณด๋งŒ ์ถ”๋ ค๋‚ธ ์š”์•ฝ ๋ฐ์ดํ„ฐ
  • React Query์˜ ๋ณต์žกํ•œ ๋‚ด๋ถ€ ์ƒํƒœ๋ฅผ JSON์ฒ˜๋Ÿผ ํด๋ผ์ด์–ธํŠธ์— ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋Š” ๋‹จ์ˆœํ•œ ํ˜•ํƒœ๋กœ ๋งŒ๋“œ๋Š” ๊ฒƒ

 

2. ๋ Œ๋”๋ง ๋‹จ๊ณ„ (์„œ๋ฒ„ + ํด๋ผ์ด์–ธํŠธ)

  • HydrationBoundary๋กœ ๋ž˜ํ•‘ํ•˜์—ฌ ์ƒํƒœ๋ฅผ ๋ณต์›:
<HydrationBoundary state={dehydratedState}> <App /> </HydrationBoundary>
  • dehydratedState๋Š” ์„œ๋ฒ„์™€ ํด๋ผ์ด์–ธํŠธ ๊ฐ๊ฐ์˜ QueryClient์— ์ดˆ๊ธฐ๊ฐ’์„ ๊ณต๊ธ‰.

๐Ÿ” ์™œ ์ด๊ฑธ ํ•ด์•ผ ํ•˜๋‚˜์š”?

React Query๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ํด๋ผ์ด์–ธํŠธ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ fetchํ•ฉ๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ SSR์—์„œ๋Š” ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฏธ๋ฆฌ ๋ฐ›์•„์˜ค๊ณ  ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ํด๋ผ์ด์–ธํŠธ๋กœ ๋„˜๊ฒจ์ค„ ์ˆ˜ ์žˆ์–ด์•ผ ํ•˜์ฃ .

๊ทธ๋ž˜์„œ:

  1. ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฏธ๋ฆฌ fetch → queryClient์— ์ €์žฅ๋จ
  2. ๊ทธ queryClient๋ฅผ dehydrate() ํ•ด์„œ JSON์œผ๋กœ ๋งŒ๋“ค๊ณ 
  3. ๊ทธ๊ฑธ ํด๋ผ์ด์–ธํŠธ์— props๋กœ ์ „๋‹ฌ
  4. ํด๋ผ์ด์–ธํŠธ๋Š” <HydrationBoundary state={dehydratedState}>๋กœ ์ด ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์‹œ ๋ณต์›(hydrate)

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ดˆ๊ธฐ ํ™”๋ฉด ๋ Œ๋”๋ง ์‹œ์ ์—์„œ ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„๊ฐ€ ๋™์ผํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋™์ž‘ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๐ŸŒŸ ์ด ๋ฐฉ์‹์˜ ์žฅ์ 

๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ ์„œ๋ฒ„์™€ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋™์ผํ•œ ์บ์‹œ ์ƒํƒœ์—์„œ ์‹œ์ž‘ํ•˜์—ฌ, hydration mismatch ๋ฐฉ์ง€
Refetch ํƒ€์ด๋ฐ ๋ช…ํ™• dataUpdatedAt์ด ๊ธฐ๋ก๋˜๋ฏ€๋กœ, ์˜ค๋ž˜๋œ ๋ฐ์ดํ„ฐ๋งŒ refetch๋จ
๋ถˆํ•„์š”ํ•œ API ํ˜ธ์ถœ ์ค„์ž„ ์ดˆ๊ธฐ ์บ์‹œ๊ฐ€ ์žˆ์–ด์„œ ํด๋ผ์ด์–ธํŠธ์—์„œ ๋ฐ”๋กœ refetch ์•ˆ ํ•ด๋„ ๋จ
์œ ์—ฐ์„ฑ ์ผ๋ถ€๋Š” prefetchํ•˜๊ณ , ๋‚˜๋จธ์ง€๋Š” ํด๋ผ์ด์–ธํŠธ์—์„œ๋งŒ fetch ๊ฐ€๋Šฅ → ํผํฌ๋จผ์Šค ์ตœ์ ํ™” ๊ฐ€๋Šฅ

 

โš ๏ธ ์ฃผ์˜ํ•  ์ 

  • initialData์™€ ๋‹ฌ๋ฆฌ dehydratedState๋Š” QueryClient ๋‚ด๋ถ€์˜ ๋ชจ๋“  ์ฟผ๋ฆฌ ์ƒํƒœ๋ฅผ ์ง๋ ฌํ™”ํ•จ.
  • getServerSideProps / getStaticProps ์™ธ์—๋„, Remix, RSC ๋“ฑ ๊ฐ ํ”„๋ ˆ์ž„์›Œํฌ๋งˆ๋‹ค ์ ์šฉ ๋ฐฉ์‹์ด ์•ฝ๊ฐ„์”ฉ ๋‹ค๋ฆ„.
  • queryClient๋Š” ์„œ๋ฒ„ prefetch์šฉ / ์„œ๋ฒ„ ๋ Œ๋”๋ง์šฉ / ํด๋ผ์ด์–ธํŠธ์šฉ ์ด 3๊ฐœ๊ฐ€ ์‚ฌ์šฉ๋จ:
    1. ์„œ๋ฒ„ prefetch ์šฉ๋„ (loader ๋‚ด๋ถ€)
    2. SSR HTML ์ƒ์„ฑ์šฉ
    3. ํด๋ผ์ด์–ธํŠธ hydration ์šฉ๋„

 

๐Ÿง  ๊ธฐ์–ตํ•  ์ 

initialData๋Š” ์ž„์‹œ ์‘๊ธ‰์ฒ˜์น˜,
dehydratedState + HydrationBoundary๋Š” ์ •ํ™•ํ•˜๊ณ  ํ™•์žฅ ๊ฐ€๋Šฅํ•œ ๊ณต์‹ ํ•ด๊ฒฐ์ฑ…์ž…๋‹ˆ๋‹ค.