๐Ÿš€ 09. Next.js App Router ํ™˜๊ฒฝ์—์„œ์˜ TanStack Query ์…‹์—…

2026๋…„ 4์›” 30์ผ ์ˆ˜์ •๋จ

๐Ÿ“‹ ๊ฐœ์š”

React Query๋ฅผ ์ตœ์‹  Next.js App Router(RSC) ํ™˜๊ฒฝ์— ํƒ‘์žฌํ•  ๋•Œ ํ•„์ˆ˜์ ์ธ QueryClientProvider ์‹ฑ๊ธ€ํ†ค ์„ธํŒ… ์ „๋žต๊ณผ ๋ฉ”๋ชจ๋ฆฌ ๋ฆญ ๋ฐฉ์ง€๋ฒ•์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

๐Ÿ“‹ ๋ชฉ์ฐจ

"์˜์ฒ  ๋‹˜, Layout.tsx ํŒŒ์ผ ์ตœ์ƒ๋‹จ์— use client๋ฅผ ๋ถ™์ด๋ฉด ๋ธŒ๋ผ์šฐ์ € ๋ฒˆ๋“ค๊ณผ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์ด ์–ด๋–ป๊ฒŒ ๋‹ฌ๋ผ์ง€๋Š”์ง€ ๋จผ์ € ํ™•์ธํ•ด๋ด…์‹œ๋‹ค."

โ˜•๏ธ ์˜์ฒ ์ด์˜ ๊ณ ๋ฏผ: "Next.js์—์„œ Query ์–ด๋–ป๊ฒŒ ์ผœ๋‚˜์š”?"

(์›”์š”์ผ ์•„์นจ, ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋ฒ„๋ฒ„๋ฒ…๊ฑฐ๋ฆฌ๋Š” ํ˜„์ƒ์„ ๊ฒช๋Š” ์˜์ฒ )

๐Ÿฃ ์˜์ฒ : ๋ฆฌ๋“œ ๋‹˜! ์ € ํ˜ผ์ž ์ฃผ๋ง์— Next.js App Router๋กœ ํ”„๋กœ์ ํŠธ ๋ผˆ๋Œ€ ๋‹ค ๋งŒ๋“ค์–ด ๋†จ๊ฑฐ๋“ ์š”? ๊ทผ๋ฐ React Query๋ฅผ app/layout.tsx์— Provider๋กœ ๊ฐ์‹ธ๋ ค๋‹ˆ๊นŒ ์—๋Ÿฌ๊ฐ€ ๋œจ๋”๋ผ๊ณ ์š”. Provider๋Š” ๋ฆฌ์•กํŠธ ํ›…์„ ์“ฐ๋‹ˆ๊นŒ ๋‹น์—ฐํžˆ ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ์ž–์•„์š”? ๊ทธ๋ž˜์„œ layout.tsx ๊ผญ๋Œ€๊ธฐ์— 'use client' ์ง€์‹œ์–ด๋ฅผ ๋ถ™์ด๊ณ  ์‹œ์ž‘ํ–ˆ์ฃ !

๐Ÿฆ ์˜ํ˜ธ: ์˜์ฒ  ๋‹˜, ๊ทธ๊ฑด App Router์˜ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ(RSC) ์ด์ ์„ ์ค„์ด๋Š” ์œ„ํ—˜ํ•œ ์„ ํƒ์ž…๋‹ˆ๋‹ค. ์ตœ์ƒ๋‹จ ๋ ˆ์ด์•„์›ƒ ์ „์ฒด๋ฅผ ํด๋ผ์ด์–ธํŠธ๋กœ ๋ฐ€์–ด๋ฒ„๋ฆฌ์…จ๊ตฐ์š”.
๋” ์‹ฌ๊ฐํ•œ ๊ฑด, const queryClient = new QueryClient() ๋ฅผ ๊ทธ๋ƒฅ ์ „์—ญ ๋ณ€์ˆ˜๋กœ ์„ ์–ธํ•˜์…จ๋„ค์š”? ์„œ๋ฒ„์—์„œ ์ด ๋ฐฉ์‹์€ ๋‹ค๋ฅธ ์œ ์ € A์˜ ์บ์‹œ๊ฐ€ ์œ ์ € B์˜ ์š”์ฒญ์— ์„ž์ผ ์ˆ˜ ์žˆ๋Š” ์ „์—ญ ๋ฉ”๋ชจ๋ฆฌ ์˜ค์—ผ(Cross-Request State Leak) ์œ„ํ—˜์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋จผ์ € ๊ตฌ์กฐ๋ถ€ํ„ฐ ๋ถ„๋ฆฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.


๐Ÿค” ์™œ ์•Œ์•„์•ผ ํ•˜๋Š”๊ฐ€: ๋‘ ์„ธ๊ณ„์˜ ์ถฉ๋Œ

SPA ์‹œ์ ˆ React ๊ฐœ๋ฐœ์ž๋“ค์€ index.tsx ์ตœ์ƒ๋‹จ ํŒŒ์ผ ๋ฐ–์— const queryClient = new QueryClient() ํ•˜๋‚˜๋ฅผ ์„ ์–ธํ•ด๋†“๊ณ  ๋Œ๋ ค์ผ์Šต๋‹ˆ๋‹ค. ๋ธŒ๋ผ์šฐ์ €๋Š” ๋ณดํ†ต ํ•œ ์‚ฌ์šฉ์ž์˜ ์„ธ์…˜ ์•ˆ์—์„œ๋งŒ ๋™์ž‘ํ•˜๋‹ˆ๊นŒ์š”.

ํ•˜์ง€๋งŒ Next.js 13+ (App Router) ๋Š” ๊ฐ•๋ ฅํ•œ Node.js ์„œ๋ฒ„ ์ฃผ๋ฐฉ์—์„œ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๊ตฌ์›Œ์ง‘๋‹ˆ๋‹ค. ์„œ๋ฒ„ ๊ณต๊ฐ„์€ ์ˆ˜๋งŒ ๋ช…์˜ ์œ ์ € ์ ‘์† ์š”์ฒญ์ด ๋™์‹œ์— ๋น—๋ฐœ์นฉ๋‹ˆ๋‹ค. ์ด ์ƒํ™ฉ์—์„œ ์„œ๋ฒ„ ๋ฉ”๋ชจ๋ฆฌ ์ƒ์˜ ์ „์—ญ ๋ณ€์ˆ˜(์‹ฑ๊ธ€ํ†ค)๋กœ QueryClient ์ธ์Šคํ„ด์Šค๋ฅผ ํ•˜๋‚˜ ๋ฉ๊ทธ๋Ÿฌ๋‹ˆ ๋งŒ๋“ค์–ด๋‘๋ฉด?
A ์‚ฌ์šฉ์ž๊ฐ€ ํŽ˜์นญํ•ด ์˜จ [user, me] ์‹ ์ƒ ์ •๋ณด ์บ์‹œ๊ฐ€ 0.1์ดˆ ๋’ค ์ ‘์†ํ•œ B ์‚ฌ์šฉ์ž์˜ ํ™”๋ฉด์— ๊ทธ๋Œ€๋กœ ํžˆํŠธ(Cache Hit) ๋˜๋ฒ„๋ฆฌ๋Š” ์น˜๋ช…์  ๊ฐœ์ธ์ •๋ณด ์œ ์ถœ ์‚ฌ๊ณ  ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

์ด ๋‹จ์›์—์„œ๋Š” RSC ์„ธ๊ณ„๊ด€์—์„œ ์™„๋ฒฝํ•˜๊ณ  ์•ˆ์ „ํ•˜๊ฒŒ React Query๋ฅผ ์„ค์น˜ํ•˜๋Š” '์ •์„(Boilerplate)' ์…‹์—…๋ฒ•์„ ์ตํž™๋‹ˆ๋‹ค.


1. ์ „์—ญ ๋ˆ„์ˆ˜ ๋ฐฉ์ง€: Request-Scoped QueryClient

์ •๋‹ต์€ "ํด๋ผ์ด์–ธํŠธ ํ™˜๊ฒฝ(๋ธŒ๋ผ์šฐ์ €)์ผ ๋•Œ๋Š” ์ „์—ญ ์‹ฑ๊ธ€ํ†ค์„ ์“ฐ๋˜, ์„œ๋ฒ„ ํ™˜๊ฒฝ์ผ ๋•Œ๋Š” ๋ฐ˜๋“œ์‹œ ์‚ฌ์šฉ์ž์˜ ์š”์ฒญ(Request) 1๋ฒˆ๋‹น 1๊ฐœ์˜ QueryClient๋ฅผ ๋…๋ฆฝ์ ์œผ๋กœ ์ฐ์–ด๋‚ธ๋‹ค(Factory)" ์ž…๋‹ˆ๋‹ค.

์ด๋Š” Next.js์™€ TanStack Query ๊ณต์‹ ๋ฌธ์„œ์—์„œ ์๊ธฐ๋ฅผ ๋ฐ•์€ ํŒฉํ† ๋ฆฌ ํŒจํ„ด์ž…๋‹ˆ๋‹ค.
๋จผ์ € ํ›… ๊ธฐ๋ฐ˜ ์ƒ์„ฑ๊ธฐ ํŒŒ์ผ์„ ๋ถ„๋ฆฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

// ๐Ÿ“ app/providers/get-query-client.ts
import { QueryClient, defaultShouldDehydrateQuery } from '@tanstack/react-query';
 
// ๋”ฑ ์ด ์˜ต์…˜ ๋ฉ์–ด๋ฆฌ ํ•˜๋‚˜๋งŒ ์ž˜ ๋งŒ๋“ค์–ด ๋‘ก๋‹ˆ๋‹ค.
function makeQueryClient() {
  return new QueryClient({
    defaultOptions: {
      queries: {
        // SSR ํ™˜๊ฒฝ์—์„œ๋Š” ๋ณดํ†ต 0 ์ด์ƒ์œผ๋กœ ๋‘ก๋‹ˆ๋‹ค.
        // 0์ด๋ฉด ์„œ๋ฒ„ ์บ์‹œ๊ฐ€ ํด๋ผ์ด์–ธํŠธ๋กœ ๋„˜์–ด๊ฐ€์ž๋งˆ์ž stale ์ƒํƒœ๊ฐ€ ๋˜์–ด ๋ฐ”๋กœ ๋‹ค์‹œ ์š”์ฒญํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
        staleTime: 60 * 1000,
      },
      dehydrate: {
        // ๋ณด๋ฅ˜ ์ค‘์ธ ํ”„๋กœ๋ฏธ์Šค๊นŒ์ง€ ํด๋ผ์ด์–ธํŠธ๋กœ Hydrateํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๋Š” ์˜ต์…˜
        shouldDehydrateQuery: (query) =>
          defaultShouldDehydrateQuery(query) ||
          query.state.status === 'pending',
      }
    },
  });
}
 
// ๐ŸŽ‰ ํด๋ผ์ด์–ธํŠธ ์‹ฑ๊ธ€ํ†ค ์•ˆ์ „๋ง ๋ณ€์ˆ˜
let browserQueryClient: QueryClient | undefined = undefined;
 
export function getQueryClient() {
  if (typeof window === 'undefined') {
    // ์„œ๋ฒ„ ํ™˜๊ฒฝ์—์„œ๋Š” ์š”์ฒญ ํ•œ ๋ฒˆ๋งˆ๋‹ค QueryClient๋ฅผ ์ƒˆ๋กœ ๋งŒ๋“ ๋‹ค.
    return makeQueryClient();
  } else {
    // ๋ธŒ๋ผ์šฐ์ € ํ™˜๊ฒฝ์—์„œ๋Š” ๋ Œ๋”๋ง์ด ๋ฐ˜๋ณต๋˜์–ด๋„ ํ•˜๋‚˜์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ์œ ์ง€ํ•œ๋‹ค.
    if (!browserQueryClient) browserQueryClient = makeQueryClient();
    return browserQueryClient;
  }
}

2. ๋ ˆ์ด์•„์›ƒ ์•ˆ์ •์„ฑ ์œ ์ง€: Provider ๊ป๋ฐ๊ธฐ ๋ถ„๋ฆฌ (Boundary)

์œ„์—์„œ ์•ˆ์ „ํ•œ QueryClient ์ƒ์„ฑ๊ธฐ๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค๋ฉด, ์ด์ œ ์ด๊ฑธ ์•ฑ์— ์ฃผ์ž…ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
layout.tsx ๊ผญ๋Œ€๊ธฐ์— 'use client'๋ฅผ ๋ถ™์ด๋ฉด ๋ฃจํŠธ ๋ ˆ์ด์•„์›ƒ์˜ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ ์ด์ ์„ ์žƒ๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค.
๋Œ€์‹ , Provider ๊ป๋ฐ๊ธฐ ์ „์šฉ ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ ๋ฅผ ์–‡๊ฒŒ ํ•˜๋‚˜ ๋ถ„๋ฆฌํ•ด์„œ ํด๋ผ์ด์–ธํŠธ ๊ฒฝ๊ณ„๋ฅผ ์ตœ์†Œํ™”ํ•ฉ๋‹ˆ๋‹ค.

// ๐Ÿ“ app/providers/react-query-provider.tsx
'use client' // โค๏ธ ์ด๊ฒƒ์€ Provider ์ „์šฉ ๊ป์งˆ์ด๋‹ค!
 
import { QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { getQueryClient } from './get-query-client';
 
export default function ReactQueryProvider({ children }: { children: React.ReactNode }) {
  // useState์˜ ์ดˆ๊ธฐํ™”๊ฐ’(Initializer) ํŒจํ„ด์„ ํ†ตํ•ด,
  // ๋ Œ๋”๋ง์ด ์ผ์–ด๋‚  ๋•Œ๋งˆ๋‹ค ํด๋ผ์ด์–ธํŠธ ๊ฐ์ฒด๋ฅผ ๋‹ค์‹œ ๊นŽ์ง€ ์•Š๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค!
  const queryClient = getQueryClient();
 
  return (
    <QueryClientProvider client={queryClient}>
      {children}
      {/* ๋ค์œผ๋กœ ๊ฐœ๋ฐœ ๋„๊ตฌ๋„ ์—ฌ๊ธฐ์„œ ์‹ฌ์–ด์ค€๋‹ค */}
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  );
}

์ด ์–‡์€ ๊ป์งˆ์„ ์™„์„ฑํ–ˆ์œผ๋‹ˆ, ๋“œ๋””์–ด ๋Œ€๋ง์˜ ์ตœ์ƒ๋‹จ ์„œ๋ฒ„ ์ฃผ๋ฐฉ์ธ layout.tsx ์— ๋ฌด์‚ฌ๊ณ  ํƒ‘์žฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// ๐Ÿ“ app/layout.tsx
// (์—ฌ๊ธด ์ˆœ์ˆ˜ Server Component ์˜์—ญ์ž…๋‹ˆ๋‹ค)
import ReactQueryProvider from './providers/react-query-provider';
 
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ko">
      <body>
        {/* ๐Ÿš€ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์˜ ๊ฑฐ๋Œ€ํ•œ ํŠธ๋ฆฌ๋ฅผ Provider ๋ฐ”๊ตฌ๋‹ˆ๋กœ ๋‹ด์•„์ฃผ๊ธฐ๋งŒ ํ•˜๋ฉด ๋! */}
        <ReactQueryProvider>
          {children}
        </ReactQueryProvider>
      </body>
    </html>
  );
}

์ด ๊ตฌ์กฐ๋Š” layout.tsx ์ž์ฒด๋ฅผ ํด๋ผ์ด์–ธํŠธ ๋ฒˆ๋“ค๋กœ ๋ฐ€์–ด ๋„ฃ์ง€ ์•Š์œผ๋ฉด์„œ, ํ•˜์œ„ ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ•„์š”ํ•œ ๊ณณ์—์„œ useQuery๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค.


๐Ÿ“ ๋ฐฐ์šด ๋‚ด์šฉ ์ ๊ฒ€ํ•˜๊ธฐ (Quiz)

Q. ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์— useState๋ฅผ ์ด์šฉํ•ด QueryClient ํด๋ผ์ด์–ธํŠธ๋ฅผ ์ดˆ๊ธฐํ™”(์ดˆ๊นƒ๊ฐ’)ํ•˜์—ฌ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด ํ›…์Šค ์›๋ฆฌ์ƒ ์˜ฌ๋ฐ”๋ฅธ ๋ฐฉ๋ฒ•์ด์ž ์•ˆ์ „ํ•œ ์ด์œ ๋Š” ๋ฌด์—‡์ž…๋‹ˆ๊นŒ?

'use client'
import { QueryClientProvider, QueryClient } from '@tanstack/react-query'
import { useState } from 'react'
 
export default function Providers({ children }: { children: React.ReactNode }) {
  // ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์—์„œ ์ƒ์„ฑ!
  const [queryClient] = useState(() => new QueryClient())
 
  return (
    <QueryClientProvider client={queryClient}>
      {children}
    </QueryClientProvider>
  )
}
  • A) useState ์•ˆ์—์„œ ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋กœ ๊ฐ์‹ธ ์ง€์—ฐ ์ดˆ๊ธฐํ™”(Lazy Initialization)๋ฅผ ํ–ˆ์œผ๋ฏ€๋กœ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์•„๋ฌด๋ฆฌ ๋ฆฌ๋ Œ๋”๋ง์„ ํ•˜๋”๋ผ๋„ ์ ˆ๋Œ€ QueryClient๋ฅผ ์žฌ์ƒ์„ฑํ•˜์ง€ ์•Š๊ณ  ๋ ˆํผ๋Ÿฐ์Šค๋ฅผ ์บ์‹ฑ(์œ ์ง€)ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
  • B) ๋ธŒ๋ผ์šฐ์ € ๋ฉ”๋ชจ๋ฆฌ์— ๋ฌดํ•œ์ • ์บ์‹œ๊ฐ€ ์Œ“์ด๋Š” ๊ฑธ ๋ง‰๊ธฐ ์œ„ํ•ด, ๋ฆฌ๋ Œ๋”๋ง๋งˆ๋‹ค ๋‚ก์€ QueryClient๊ฐ€ ๋ฎ์–ด์จ์ ธ ์ดˆ๊ธฐํ™”๋˜๋„๋ก ์ˆ˜๋ช…์„ ์ œํ•œํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
  • C) ์ € ์ฝ”๋“œ๋Š” ํ‹€๋ฆฐ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค. useMemo๋ฅผ ๋ฐ˜๋“œ์‹œ ์‚ฌ์šฉํ•ด์•ผ ๋™์ผํ•œ ์ธ์Šคํ„ด์Šค๊ฐ€ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.

โœ… ์ •๋‹ต: A

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:

  • ์›๋ฆฌ ์„ค๋ช…: new QueryClient()๋ฅผ ์„œ๋ฒ„ ๋ชจ๋“ˆ ์ „์—ญ์— ๋‘๋ฉด ์š”์ฒญ ๊ฐ„ ์บ์‹œ ๊ณต์œ  ์œ„ํ—˜์ด ์ƒ๊ธฐ๊ณ , ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ ๋ณธ๋ฌธ์—์„œ ๋งค ๋ Œ๋”๋งˆ๋‹ค ๋งŒ๋“ค๋ฉด ์บ์‹œ ์ธ์Šคํ„ด์Šค๊ฐ€ ๊ณ„์† ์ดˆ๊ธฐํ™”๋ฉ๋‹ˆ๋‹ค. useState(() => new QueryClient())์ฒ˜๋Ÿผ ์ง€์—ฐ ์ดˆ๊ธฐํ™”ํ•˜๋ฉด ๋ธŒ๋ผ์šฐ์ € ์ƒ๋ช…์ฃผ๊ธฐ ๋™์•ˆ ๊ฐ™์€ ์ธ์Šคํ„ด์Šค๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์˜ค๋‹ต ํ”ผ๋“œ๋ฐฑ: "์˜์ฒ  ๋‹˜, B๋ฒˆ์ฒ˜๋Ÿผ ๋ฆฌ๋ Œ๋”๋ง๋งˆ๋‹ค QueryClient๊ฐ€ ์ดˆ๊ธฐํ™”๋˜๋ฉด ์บ์‹œ๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์บ์‹œ๋Š” ์•ˆ์ •์ ์ธ ์ธ์Šคํ„ด์Šค ์œ„์—์„œ ์˜๋ฏธ๊ฐ€ ์ƒ๊น๋‹ˆ๋‹ค."
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: ์ธ์Šคํ„ด์Šค ์ƒ๋ช… ์—ฐ์žฅ์ˆ ์˜ ๊ฝƒ, useState(() => init) ์ง€์—ฐ ์ดˆ๊ธฐํ™”(Lazy Init)!

๐Ÿ“ ๋งˆ๋ฌด๋ฆฌ ํ€ด์ฆˆ

Q1. Next.js App Router์—์„œ QueryClient๋ฅผ ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง๋งˆ๋‹ค ์ƒˆ๋กœ ๋งŒ๋“ค๋ฉด ์–ด๋–ค ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธฐ๋‚˜์š”?

โœ… ์ •๋‹ต: ๋ Œ๋”๋ง ๋•Œ๋งˆ๋‹ค ์บ์‹œ ์ €์žฅ์†Œ๊ฐ€ ๋ฐ”๋€Œ์–ด ๊ธฐ์กด ์บ์‹œ๊ฐ€ ์œ ์ง€๋˜์ง€ ์•Š๊ณ  ๋ถˆํ•„์š”ํ•œ ์žฌ์š”์ฒญ๊ณผ ์ƒํƒœ ์ดˆ๊ธฐํ™”๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:
QueryClient๋Š” ์บ์‹œ ๊ธˆ๊ณ ์ž…๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ์—์„œ๋Š” ์•ˆ์ •์ ์ธ ์ธ์Šคํ„ด์Šค๋ฅผ ์œ ์ง€ํ•ด์•ผ ๊ฐ™์€ ํ‚ค์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Provider ์ปดํฌ๋„ŒํŠธ ์•ˆ์—์„œ useState(() => new QueryClient()) ๊ฐ™์€ ํŒจํ„ด์„ ์“ฐ๋Š” ์ด์œ ๋„ ์ธ์Šคํ„ด์Šค ์•ˆ์ •์„ฑ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

Q2. App Router์—์„œ TanStack Query Provider ํŒŒ์ผ์— 'use client'๊ฐ€ ํ•„์š”ํ•œ ์ด์œ ๋Š” ๋ฌด์—‡์ธ๊ฐ€์š”?

โœ… ์ •๋‹ต: QueryClientProvider์™€ useQuery ๊ธฐ๋ฐ˜ ์บ์‹œ ๊ตฌ๋…์€ ํด๋ผ์ด์–ธํŠธ ๋Ÿฐํƒ€์ž„์—์„œ ๋™์ž‘ํ•ด์•ผ ํ•˜๋ฏ€๋กœ ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ ๊ฒฝ๊ณ„๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:
์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ๋Š” ๋ธŒ๋ผ์šฐ์ € ์ƒํƒœ๋ฅผ ๊ตฌ๋…ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. Query Provider๋Š” ํด๋ผ์ด์–ธํŠธ ์บ์‹œ์™€ ํ›…์„ ์œ„ํ•œ ๊ธฐ๋ฐ˜์ด๋ฏ€๋กœ ํด๋ผ์ด์–ธํŠธ ๊ฒฝ๊ณ„ ์•ˆ์— ๋‘ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค๋งŒ ์„œ๋ฒ„์—์„œ ํ”„๋ฆฌํŽ˜์น˜ํ•œ ๊ฒฐ๊ณผ๋Š” ์ดํ›„ HydrationBoundary๋กœ ๋„˜๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Q3. ์˜์ฒ ์ด์˜ ํ…Œ์ŠคํŠธ ํƒ€์ž„: ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฏธ๋ฆฌ ๊ฐ€์ ธ์˜จ ๋ฐ์ดํ„ฐ๊ฐ€ ํด๋ผ์ด์–ธํŠธ ์ง„์ž… ์งํ›„ ๋‹ค์‹œ fetch๋ฉ๋‹ˆ๋‹ค. ๊ฐ€์žฅ ๋จผ์ € ์ ๊ฒ€ํ•  ์„ค์ •์€ ๋ฌด์—‡์ธ๊ฐ€์š”?

โœ… ์ •๋‹ต: ํ”„๋ฆฌํŽ˜์น˜๋œ ๋ฐ์ดํ„ฐ์— ๋งž๋Š” staleTime์ด ์„ค์ •๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:
TanStack Query๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๊ฐ€ ์ฆ‰์‹œ stale์ž…๋‹ˆ๋‹ค. SSR ์งํ›„ ๊ณง๋ฐ”๋กœ ์žฌ์š”์ฒญ์ด ๋‚ ์•„๊ฐ€๋Š” ๊ฒƒ์ด ํ•ญ์ƒ ๋ฒ„๊ทธ๋Š” ์•„๋‹ˆ์ง€๋งŒ, โ€œ๋ฐฉ๊ธˆ ์„œ๋ฒ„์—์„œ ๋ฐ›์•„์˜จ ๋ฐ์ดํ„ฐโ€๋ผ๋ฉด ์ ์ ˆํ•œ ์‹ ์„ ๋„ ์‹œ๊ฐ„์„ ์ฃผ์–ด ์ค‘๋ณต ์š”์ฒญ์„ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿฃ ์˜์ฒ ์ด์˜ ํ‡ด๊ทผ ์ผ๊ธฐ

์˜ค๋Š˜์€ QueryClient ์ธ์Šคํ„ด์Šค์˜ ์œ„์น˜๊ฐ€ ๋ณด์•ˆ๊ณผ ์„ฑ๋Šฅ์„ ๋™์‹œ์— ์ขŒ์šฐํ•œ๋‹ค๋Š” ๊ฑธ ๋ฐฐ์› ๋‹ค. ๋ธŒ๋ผ์šฐ์ €์—์„œ๋Š” ํ•˜๋‚˜์˜ ์•ˆ์ •์ ์ธ ํด๋ผ์ด์–ธํŠธ๋ฅผ ์žฌ์‚ฌ์šฉํ•ด์•ผ ์บ์‹œ๊ฐ€ ์œ ์ง€๋˜์ง€๋งŒ, ์„œ๋ฒ„์—์„œ๋Š” ์š”์ฒญ๋ณ„๋กœ ์ƒˆ ํด๋ผ์ด์–ธํŠธ๋ฅผ ๋งŒ๋“ค์–ด์•ผ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๊ฐ€ ์„ž์ด์ง€ ์•Š๋Š”๋‹ค.

๐Ÿ’ก ์˜ค๋Š˜์˜ ๊ตํ›ˆ: "์ฝ”๋“œ๊ฐ€ ์„œ๋ฒ„์—์„œ ๋„๋Š”์ง€ ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋„๋Š”์ง€ ๋จผ์ € ํ™•์ธํ•˜์ž. QueryClient๋Š” ํด๋ผ์ด์–ธํŠธ์—์„œ๋Š” ์•ˆ์ •์ ์œผ๋กœ ์žฌ์‚ฌ์šฉํ•˜๊ณ , ์„œ๋ฒ„์—์„œ๋Š” ์š”์ฒญ๋งˆ๋‹ค ์ƒˆ๋กœ ๋งŒ๋“ ๋‹ค."

๋‚ด์ผ์€ Provider๋ฅผ ๋งŒ๋“ค ๋•Œ layout.tsx์— ๋ฐ”๋กœ 'use client'๋ฅผ ๋ถ™์ด์ง€ ๋ง๊ณ , ์–‡์€ provider ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋”ฐ๋กœ ๋งŒ๋“  ๋’ค getQueryClient()์˜ ์„œ๋ฒ„/๋ธŒ๋ผ์šฐ์ € ๋ถ„๊ธฐ๋ฅผ ๋จผ์ € ํ™•์ธํ•ด์•ผ๊ฒ ๋‹ค.