๐Ÿš€ 10. Next.js SSR/SSG ํ™˜๊ฒฝ์˜ ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ ์ฃผ์ž… (Hydration)

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

๐Ÿ“‹ ๊ฐœ์š”

์„œ๋ฒ„๊ฐ€ ๋ฏธ๋ฆฌ ๊ตฌ์›Œ์ค€ ๋ฐ์ดํ„ฐ ์บ์‹œ๋ฅผ ํด๋ผ์ด์–ธํŠธ ์บ์‹œ๋กœ ์–ด๋–ป๊ฒŒ ์•ˆ์ „ํ•˜๊ฒŒ ๋„˜๊ฒจ์ฃผ์–ด(Hydration), ํด๋ผ์ด์–ธํŠธ์˜ ๋ถˆํ•„์š”ํ•œ ์Šคํ”ผ๋„ˆ๋ฅผ ์™„๋ฒฝ ์‚ญ์ œํ•˜๋Š”์ง€ ๋ฐฐ์›๋‹ˆ๋‹ค.

๐Ÿ“‹ ๋ชฉ์ฐจ

"์˜์ฒ  ๋‹˜, ์„œ๋ฒ„ ๋ Œ๋”๋ง(SSR) ๋‹ค ํ•ด๋†“๊ณ  ์™œ ๋ธŒ๋ผ์šฐ์ € ์ผœ์ง€์ž๋งˆ์ž ๋ฐ์ดํ„ฐ๋ฅผ ๋˜ Fetch ํ•˜๋ ค๊ณ  ๋น„๋น„์ ๋Œ€๋‚˜์š”? ์ˆ˜ํ™”(Hydration)๊ฐ€ ๋Š๊ฒผ์ž–์•„์š”!"

โ˜•๏ธ ์˜์ฒ ์ด์˜ ๊ณ ๋ฏผ: "๋ถ„๋ช…ํžˆ Next.js๊ฐ€ ์งฑ ๋น ๋ฅด๋‹ค ํ–ˆ๋Š”๋ฐ..."

(๊ธˆ์š”์ผ ์•„์นจ, ํฌ๋กฌ ํผํฌ๋จผ์Šค ํƒญ์„ ์ผœ๊ณ  ์ ˆ๋งํ•˜๋Š” ์˜์ฒ )

๐Ÿฃ ์˜์ฒ : ์•„๋‹ˆ ๋ฆฌ๋“œ ๋‹˜! ์ € ์–ด์ œ ๋ฐฐ์šด ๋Œ€๋กœ Next.js App Router ์…‹์—… ๋‹ค ๋๋ƒˆ๊ฑฐ๋“ ์š”? ๊ทธ๋ฆฌ๊ณ  ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ ์•ˆ์—์„œ fetch ์น˜๋‹ˆ๊นŒ ํ™”๋ฉด์ด 0์ดˆ ๋งŒ์— ๊ฒ๋‚˜ ๋นก๋นกํ•˜๊ฒŒ ๋ Œ๋”๋ง๋ผ์„œ ์œ ์ €ํ•œํ…Œ HTML ๋”ฑ ๋–จ์–ด์ง€๋Š” ๊ฑฐ ์™„์ „ ๊ฐ๋™ํ–ˆ์–ด์š”!

๐Ÿฆ ์˜ํ˜ธ: ๊ทธ๋Ÿผ ์„ฑ๊ณต์ ์ธ๋ฐ์š”? ๋ญ๊ฐ€ ๋ถˆ๋งŒ์ด์‹œ์ฃ ?

๐Ÿฃ ์˜์ฒ : ์•„ ๊ทธ๊ฒŒ... ๊ทธ ์„œ๋ฒ„์—์„œ ๊ทธ๋ฆฐ ํ™”๋ฉด ์•ˆ์—๋‹ค๊ฐ€ "์ข‹์•„์š” ๋ฒ„ํŠผ"์ด๋‚˜ ๋ฌดํ•œ ์Šคํฌ๋กค ๋„ฃ์œผ๋ ค๊ณ  ์ž์‹ ์ปดํฌ๋„ŒํŠธ(Client Component)๋กœ ์กฐ๊ฐ ํŒŒ์„œ ๋†จ๊ฑฐ๋“ ์š”? ๊ฑฐ๊ธฐ ์•ˆ์— ๋ฆฌ์•กํŠธ ์ฟผ๋ฆฌ useQuery(['posts'])๋ฅผ ์„ ์–ธํ•˜๋‹ˆ๊นŒ, ์œ ์ €๊ฐ€ ํŽ˜์ด์ง€ ๋“ค์–ด์™€์„œ ํ™”๋ฉด ๋ณด์ž๋งˆ์ž ๋ฐฑ๊ทธ๋ผ์šด๋“œ API๋ฅผ ๋‹ค์‹œ ๋ฎ์–ด์”๋‹ˆ๋‹ค! ๋ถ„๋ช…ํžˆ ๊ทธ posts ๋ฐ์ดํ„ฐ ์œ„์ชฝ 100์ค„์งœ๋ฆฌ ๋ถ€๋ชจ ์„œ๋ฒ„ ์ฃผ๋ฐฉ์—์„œ ๋‹ค ๊ตฌ์›Œ์„œ HTML๋กœ ๋‚ด๋ฆฐ ์ตœ์‹  ๋ฐ์ดํ„ฐ์ธ๋ฐ, ํ”„๋ก ํŠธ ์—”์ง„ ์ผœ์ง€์ž๋งˆ์ž ์ง€๊ฐ€ ๋ชจ๋ฅด๊ณ  ๋‹ค์‹œ ๊ฐ€์ ธ์˜ค์ž–์•„์š”! ์„œ๋ฒ„ ๋ถ€ํ•˜ ๊ณผ๋„ํ•œ ๊ฑฐ ์•„๋‹ˆ์—์š”?!

๐Ÿฆ ์˜ํ˜ธ: ์•„ํ•˜, ์™„๋ฒฝํ•ฉ๋‹ˆ๋‹ค ์˜์ฒ  ๋‹˜. ์„œ๋ฒ„์˜ ์ฃผ๋ฐฉ์—์„œ ๋ฐ์ดํ„ฐ ์—ด์‹ฌํžˆ ํผ ์™€์„œ ์š”๋ฆฌ๋ฅผ ๊ตฌ์›Œ๋†“๊ณ , ์ •์ž‘ ํ”„๋ก ํŠธ์—”๋“œ ์•ˆ์˜ ์บ์‹œ ๊ธˆ๊ณ (QueryCache) ์—๊ฒŒ๋Š” "์„œ๋ฒ„๊ฐ€ ๋ฐฉ๊ธˆ ๋ฐ์ดํ„ฐ ๊ตฌ์›Œ๋†จ์œผ๋‹ˆ ๋„ˆ ์ด๊ฑฐ ์•Œ๊ณ  ์žˆ์–ด๋ผ" ๋ผ๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ์ž…์‹œ์ผœ์ฃผ์งˆ ์•Š์•˜๊ตฐ์š”.
๊ฐ€์žฅ ์ค‘์š”ํ•˜๊ณ  ๋‚œ์ด๋„ ์žˆ๋Š” ๋Œ€๋ฏธ์˜ ์žฅ, Hydration (์ˆ˜ํ™”) ์™€ initialData ์˜ ์„ธ๊ณ„์— ์˜ค์‹  ๊ฑธ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค.


๐Ÿค” ์™œ ์•Œ์•„์•ผ ํ•˜๋Š”๊ฐ€: ์บ์‹œ ์‚ฌ๊ฐ์ง€๋Œ€ (Cache Blind Spot)

Next.js์˜ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ(RSC)์—์„œ ๋ถ€๋ฆฌ๋‚˜์ผ€ DB์—์„œ ์กฐํšŒํ•ด ์ตœ์‹ ์˜ ์™„์ „ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค ๊ฐ€์ ธ์™€์„œ HTML์„ ๊ทธ๋ ธ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ์‹œ๋‹ค. ์ด ํ™”๋ฉด ๊ป๋ฐ๊ธฐ(HTML)๋ฅผ ์œ ์ €์˜ ๋ชจ๋ฐ”์ผ ํฐ์œผ๋กœ ๋นก ๋˜์กŒ์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ๊ทธ ํฐ ์•ˆ์—๋Š” React Query๋ผ๋Š” ๋…์ž์ ์ธ ํด๋ผ์ด์–ธํŠธ ๋งค๋‹ˆ์ €๊ฐ€ ์ƒˆ๋กœ ๋ˆˆ์„ ๋œน๋‹ˆ๋‹ค.
๋Œ€์ƒ์ด ์บ์‹œ ๊ธˆ๊ณ  ๋ฌธ์„ ์—ด์–ด๋ณด๋‹ˆ...? ํ…… ๋น„์–ด์žˆ์Šต๋‹ˆ๋‹ค.
์™œ? ์„œ๋ฒ„๊ฐ€ HTML๋งŒ ์คฌ์ง€, ํ”„๋ก ํŠธ์—”๋“œ์˜ ์ฟผ๋ฆฌ ํด๋ผ์ด์–ธํŠธ(๋ฉ”๋ชจ๋ฆฌ) ์•ˆ์— "๋‚ด๊ฐ€ ๋ฌด์Šจ ๋ฐ์ดํ„ฐ๋ฅผ ์ผ๋Š”์ง€" ์ฃผ์ž…(Inject) ํ•ด์ฃผ์ง„ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์ด์ฃ .

๊ฒฐ๊ตญ React Query๋Š” "์˜ค ๋งˆ์ด ๊ฐ“, ๋ฐ์ดํ„ฐ๊ฐ€ ํ…… ๋น„์—ˆ๋„ค? ๋‚ด๊ฐ€ ๋‹ค์‹œ API ์š”์ฒญํ•ด์„œ ๊ฐ€์ ธ์™€์•ผ๊ฒ ๊ตฐ!" ํ•˜๋ฉฐ ๋ฌด์˜๋ฏธํ•œ ์ค‘๋ณต ๋„คํŠธ์›Œํฌ ํŠธ๋ž˜ํ”ฝ์„ ์œ ๋ฐœํ•ฉ๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ์ด๋ฅผ ๋ง‰๊ธฐ ์œ„ํ•ด ์„œ๋ฒ„์—์„œ ๋”ด ๋ฐ์ดํ„ฐ๋ฅผ ํด๋ผ์ด์–ธํŠธ ์บ์‹œ๋กœ ์™ ์ง‘์–ด๋„ฃ์–ด ์ฃผ๋Š” ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ ์ฃผ์ž… ๊ธฐ์ˆ  ์„ ๋งˆ์Šคํ„ฐํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋ฐฉ๋ฒ•์€ ํฌ๊ฒŒ 2๊ฐ€์ง€์ž…๋‹ˆ๋‹ค.


1. ์ „์ˆ  A: initialData (๊ฐ€์žฅ ์‰ฝ๊ณ  ์ง๊ด€์ ์ธ Prop ์ „๋‹ฌ)

TkDodo์˜ ์•„ํ‹ฐํด #17์„ ๋ณด๋ฉด ์ฒซ ๋ฒˆ์งธ ๋ฐฉ๋ฒ•์ด ์ œ์ผ ์‰ฝ์Šต๋‹ˆ๋‹ค.
์šฐ๋ฆฌ๊ฐ€ RSC(์„œ๋ฒ„ ์ฃผ๋ฐฉ) ์•ˆ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋”ฑ ์–ป์–ด๋ƒˆ๋‹ค๋ฉด, ๊ทธ๊ฑธ ์ž์‹ ์กฐ๊ฐ ์ปดํฌ๋„ŒํŠธ(Client Component) ํ•œํ…Œ props๋กœ ๋‚ด๋ ค์ฐ์€ ๋’ค, ์ฟผ๋ฆฌ์˜ initialData ์„ค์ • ์Šฌ๋กฏ์— ํˆญ ๋ผ์›Œ ๋„ฃ๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

// ๐Ÿ“ app/board/page.tsx (์„œ๋ฒ„ ์ฃผ๋ฐฉ - RSC)
import { getPosts } from '@/api/posts'
import { BoardClient } from './board-client'; // ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ (use client)
 
export default async function BoardPage() {
  // 1๏ธโƒฃ ์„œ๋ฒ„์—์„œ DB๋ฅผ ์ง์ ‘ ์กฐํšŒํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ์™„๋ฒฝํ•˜๊ฒŒ ๊ตฌ์›Œ๋ƒ„
  const postsFromServer = await getPosts();
 
  // 2๏ธโƒฃ ๊ทธ ๊ท€ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ํ”„๋กœํผํ‹ฐ(Prop)๋กœ ํƒ๋ฐฐ๋ฅผ ์‹ธ์„œ ์ž์‹์—๊ฒŒ ๋˜์ง„๋‹ค!
  return <BoardClient initialPosts={postsFromServer} />
}
// ๐Ÿ“ app/board/board-client.tsx (ํด๋ผ์ด์–ธํŠธ ํฐ - CSR)
'use client'
 
import { useQuery } from '@tanstack/react-query';
import { getPosts } from '@/api/posts';
 
export function BoardClient({ initialPosts }: { initialPosts: Post[] }) {
  const { data } = useQuery({
    queryKey: ['posts'],
    queryFn: () => getPosts(),
    // 3๏ธโƒฃ ๐Ÿš€ ๋งˆ๋ฒ•์˜ ํ•ต์‹ฌ: "Query์•ผ! ๋‚ด ์—„๋งˆ(์„œ๋ฒ„ ์ฃผ๋ฐฉ)๊ฐ€ ๋ฐ์ดํ„ฐ ์‹ธ์ฃผ์…จ์–ด! ์บ์‹œํ†ต์— ์ผ๋‹จ ์ด๊ฑฐ๋ถ€ํ„ฐ ๋„ฃ๊ณ  ์‹œ์ž‘ํ•ด!"
    initialData: initialPosts,
  });
 
  return (
    <ul> {/* ๊นœ๋นก์ž„ 0%, API ๋ถˆํ•„์š” ์ค‘๋ณต ํ˜ธ์ถœ 0๋ฐฐ ๊ธ‰์ฆ์  ์„ฑ๋Šฅ */}
      {data.map(post => <li key={post.id}>{post.title}</li>)}
    </ul>
  )
}

๐Ÿ’ก ์žฅ๋‹จ์ 

  • ์žฅ์ : ๊ฐœ๋ฐœ์ž๊ฐ€ ์ง๊ด€์ ์œผ๋กœ ์ดํ•ดํ•˜๊ธฐ ์‰ฝ๋‹ค.
  • ๋‹จ์ : ์ปดํฌ๋„ŒํŠธ ํŠธ๋ฆฌ๊ฐ€ ๋„ˆ๋ฌด๋‚˜ ๊น๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฅผ 3๋‹จ๊ณ„, 4๋‹จ๊ณ„ ์•„๋ž˜ ๋„ํŠธ๋จธ๋ฆฌ์— ์žˆ๋Š” ์ž‘์€ ๋ฆฌ์ŠคํŠธ ์ปดํฌ๋„ŒํŠธํ•œํ…Œ ์ „๋‹ฌํ•˜๋ ค๋ฉด Props Drilling ๋ถ€๋‹ด์ด ํŽผ์ณ์ง‘๋‹ˆ๋‹ค.

2. ์ „์ˆ  B: <HydrationBoundary> (์ง„์ •ํ•œ ์•„ํ‚คํ…์ฒ˜์˜ ๊ฝƒ)

๊ทธ๋ž˜์„œ ํƒ„์ƒํ•œ ํ•ต์‹ฌ ์ „๋žต๊ฐ€ ํ’ˆ๊ฒฉ ์žˆ๋Š” Hydration(Dehydrate/Hydrate) ๋ฉ”์ปค๋‹ˆ์ฆ˜์ž…๋‹ˆ๋‹ค.
์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค ๋”ด ๋‹ค์Œ์—, ๊ทธ ๋ฐ์ดํ„ฐ๋ฅผ ์Šค๋ƒ…์ƒท(DehydratedState)์œผ๋กœ ์ง๋ ฌํ™” ์‹œ์ผœ์„œ ๋ธŒ๋ผ์šฐ์ €๋กœ ๋‚ด๋ ค๋ณด๋ƒ…๋‹ˆ๋‹ค.
๋ธŒ๋ผ์šฐ์ €์— ๋„์ฐฉํ•œ dehydrated state๋Š” <HydrationBoundary>๋ฅผ ๋งŒ๋‚˜ ์ „์—ญ ์บ์‹œ(QueryClient)์— ๋‹ค์‹œ ์ฑ„์›Œ์ง‘๋‹ˆ๋‹ค(Hydrate). ์ดํ›„ ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ๋Š” props๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ด์–ด๋ฐ›์ง€ ์•Š์•„๋„ ๊ฐ™์€ queryKey๋กœ ์บ์‹œ๋ฅผ ์ฝ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Props๋กœ ์ „๋‹ฌ ํ•จ์ˆ˜์ฒ˜๋Ÿผ ์ผ์ผ์ด ์ „๋‹ฌํ•  ํ•„์š”๊ฐ€ ์•„์˜ˆ ์‚ฌ๋ผ์ง€๋Š” ๊ฒ๋‹ˆ๋‹ค! (์˜ค๋กœ์ง€ ์บ์‹œ ์ด๋ฆ„ํ‘œ์ธ queryKey ๋งŒ์œผ๋กœ ์ฝ์–ด์˜ด)

// ๐Ÿ“ app/board/page.tsx (์„œ๋ฒ„ ์ฃผ๋ฐฉ - RSC)
import { HydrationBoundary, dehydrate, QueryClient } from '@tanstack/react-query'
import { getPosts } from '@/api/posts'
import { BoardClient } from './board-client' // ์ด๊ฑด ์ด์ œ Prop์ด ์ „ํ˜€ ํ•„์š” ์—†์ง€!
 
export default async function BoardPage() {
  // 1๏ธโƒฃ ์ด ์š”์ฒญ ๋‹จ ํ•œ ๋ฒˆ์—๋งŒ ์“ฐ์ด๋Š” 1ํšŒ์šฉ ๋นˆ ๊นกํ†ต ์ƒ์„ฑ
  const queryClient = new QueryClient();
 
  // 2๏ธโƒฃ ๋ฏธ๋ฆฌ ๋ฐ์ดํ„ฐ๋ฅผ ์ถฉ๋ถ„ํžˆ ๋‹น๊ฒจ์„œ(Prefetch) ์„œ๋ฒ„์šฉ ๋นˆ ๊นกํ†ตํ†ต ์บ์‹œ ์•ˆ์— ๊ฐ€๋“ ์ฑ„์šด๋‹ค! (queryKey ๋ช…์‹ฌ!)
  await queryClient.prefetchQuery({
    queryKey: ['posts'],
    queryFn: getPosts,
  });
 
  return (
    // 3๏ธโƒฃ dehydrate(๊นกํ†ต ์••์ถ•๊ธฐ)๋ฅผ ์š”์ฒญํ•ด์„œ ํด๋ผ์ด์–ธํŠธ๋กœ ๋ฐ€์–ด๋ฒ„๋ฆฐ๋‹ค!
    // ์ „์ž๋ ˆ์ธ์ง€(Boundary) ๊ฐ€ ๋Œ์•„๊ฐ€๋Š” ์ด ์•„๋ž˜์ชฝ(์ž์‹) ์˜์—ญ์€ ์ด์ œ ์‹ ๋“ค์˜ ์˜ํ† ๋‹ค.
    <HydrationBoundary state={dehydrate(queryClient)}>
      <BoardClient />
    </HydrationBoundary>
  )
}

์ด์ œ ํด๋ผ์ด์–ธํŠธ ์ชฝ์„ ๋ณผ๊นŒ์š”? ๋ˆˆ๋ฌผ์ด ์™ˆ์นต ๋‚  ๋งŒํผ ๊น”๋”ํ•ด์ง‘๋‹ˆ๋‹ค.

// ๐Ÿ“ app/board/board-client.tsx (ํด๋ผ์ด์–ธํŠธ - CSR)
'use client'
 
import { useQuery } from '@tanstack/react-query'
import { getPosts } from '@/api/posts'
 
export function BoardClient() {
  // ๐Ÿš€ ๋!! Prop์ด ์—†์Šต๋‹ˆ๋‹ค! initialData๋„ ์—†์Šต๋‹ˆ๋‹ค!
  // ์•„๊นŒ ๋ฐ–์—์„œ ์ „์ž๋ ˆ์ธ์ง€(Boundary)๊ฐ€ ํ„ฐ์งˆ ๋•Œ ์ด๋ฏธ ์ด ๋ธŒ๋ผ์šฐ์ € ์ „์—ญ ์บ์‹œ ์•ˆ์—
  // ['posts'] ๋ผ๋Š” ํ‚ค ๊ฐ’์œผ๋กœ ์บ์‹œ๊ฐ€ "์•„์ฃผ ๊ฝ‰ ์ฐจ" ๋“ค์–ด๊ฐ”๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
  const { data } = useQuery({
    queryKey: ['posts'],
    queryFn: () => getPosts()
  });
 
  return <div>{data.length}๊ฐœ ๊ทธ๋ฆฌ๊ธฐ ์™„๋ฃŒ</div>
}

์ด ํŒจํ„ด์ด ๋ฐ”๋กœ Next.js App Router์™€ React Query๋ฅผ ๊ฒฐํ•ฉ์‹œํ‚ค๋Š” ๊ถ๊ทน์˜ ์—”๋“œ๊ฒŒ์ž„ ์•„ํ‚คํ…์ฒ˜ ์ž…๋‹ˆ๋‹ค. ์–ด๋–ค ์ž์‹ ์ปดํฌ๋„ŒํŠธ ๊นŠ์ˆ™ํ•œ ๊ณณ์—์„œ useQuery(['posts']) ๋ฅผ ์ณ๋ถ€๋ฅด๋”๋ผ๋„ ์บ์‹œ์— ์ด๋ฏธ ๊ฟ€๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐœ๋ผ์ ธ ์žˆ์–ด์„œ ๋ฌด์ค‘๋ณต ๋ฌด๋กœ๋”ฉ ์™„๋ฒฝ ์„ฑ๋Šฅ์ด ๋‚˜์˜ต๋‹ˆ๋‹ค.


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

Q. ์˜์ฒ ์ด๊ฐ€ ์ž๋ž‘์Šค๋Ÿฝ๊ฒŒ <HydrationBoundary> ํŒจํ„ด์œผ๋กœ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ–ˆ๋Š”๋ฐ, ์ปดํฌ๋„ŒํŠธ ์ง„์ž… ์ฆ‰์‹œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๋„คํŠธ์›Œํฌ ์žฌํ˜ธ์ถœ(Refetching) ์ด ํ•œ ๋ฒˆ ๋” ๋ฐœ์ƒํ•˜๋Š” ๊ฑธ ๋ชฉ๊ฒฉํ–ˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰, ์„œ๋ฒ„ ๋ Œ๋”๋ง์— ์„ฑ๊ณตํ–ˆ์Œ์—๋„ ํด๋ผ์ด์–ธํŠธ์—์„œ ํ•œ ๋ฒˆ ๋‹ค์‹œ ์˜๋Š” ๊ธฐ์ดํ•œ ๋ฒ„๊ทธ์ธ๋ฐ, ์›์ธ์ด ๋ฌด์—‡์ผ๊นŒ์š”? ์ด ์˜ต์…˜์˜ ๊ธฐ๋ณธ๊ฐ’ ์ฒ ํ•™์— ๋น„์ถ”์–ด ์„ค๋ช…ํ•ด๋ณด์„ธ์š”.

โœ… ์ •๋‹ต: ์„œ๋ฒ„์—์„œ ํ”„๋ฆฌํŽ˜์น˜ํ•˜์—ฌ ๋„˜๊ฒจ์ค€ ๋ฐ์ดํ„ฐ(ํ†ต์กฐ๋ฆผ)์˜ 'staleTime'์ด 0(๊ธฐ๋ณธ๊ฐ’)์ด์—ˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

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

  • ์›๋ฆฌ ์„ค๋ช…: ์„œ๋ฒ„๊ฐ€ ์•„๋ฌด๋ฆฌ ์—ด์‹ฌํžˆ prefetchQuery ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ฑ„์›Œ์ฃผ๊ณ , ํด๋ผ์ด์–ธํŠธ๋กœ Hydrate(์ˆ˜ํ™”) ๋„˜๊ฒจ์ค˜ ๋ดค์ž React Query์˜ ์‹ ์„ ๋„(staleTime) ๊ธฐ๋ณธ๊ฐ’์€ 0์ดˆ์ž…๋‹ˆ๋‹ค! ๋ธŒ๋ผ์šฐ์ €์˜ ์ฟผ๋ฆฌ ๋Œ€์ƒ์€ ๋ˆˆ์„ ๋œจ์ž๋งˆ์ž "์˜ค ๋ฐ์ดํ„ฐ ๋“ค์–ด์žˆ๋„ค?(์Šคํ”ผ๋„ˆ ๋ง‰์Œ, ์ผ๋‹จ ํ™”๋ฉด ๊ทธ๋ฆผ) ์ž ๊น, ๊ทผ๋ฐ ์ด๊ฑฐ stale(์ƒํ•œ ๋นต) ์ƒํƒœ์ž–์•„? ๋‹น์žฅ ์„œ๋ฒ„์— ๋’ท๋‹จ์—์„œ ๋‹ค์‹œ ๊ฐ€์„œ ์ง„์งœ ์ตœ์‹  ๋งž๋Š”์ง€ ๋ฌผ์–ด๋ด์•ผ๊ฒ ๋‹ค!" ๋ผ๋ฉฐ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ๋ฆฌํŒจ์นญ์„ ์กฐ์šฉํžˆ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  • ์˜ค๋‹ต ํ”ผ๋“œ๋ฐฑ: "์˜์ฒ  ๋‹˜, ์ดˆ๊ธฐ Hydrate ์งํ›„ ๋ถˆํ•„์š”ํ•œ ๋’ท๋‹จ ๋ฆฌํŒจ์นญ(Refetch)๋ฅผ ๋ง‰๊ณ  ์‹ถ๋‹ค๋ฉด, ์„œ๋ฒ„์—์„œ Prefetch ํ•  ๋•Œ๋ผ๋˜๊ฐ€ ์ตœ์ƒ๋‹จ QueryClient ์ธ์Šคํ„ด์Šค๋ฅผ ์ฐ์–ด๋‚ผ ๋•Œ staleTime: 60 * 1000 (1๋ถ„ ์ •๋„) ์˜ ๋„‰๋„‰ํ•œ ์‹ ์„ ๋„ ์œ ํ†ต๊ธฐํ•œ์„ ๊ฐ•์ œ๋กœ ๋ถ€์—ฌํ•ด์ฃผ์…”์•ผ๋งŒ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ž ์‹œ ์˜์‹ฌ์„ ๊ฑฐ๋‘๊ณ  ๋„คํŠธ์›Œํฌ ๋กœ๋“œ๋ฅผ ์ ˆ๋ฌ˜ํ•˜๊ฒŒ ์•„๋‚๋‹ˆ๋‹ค!"
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: ์ˆ˜ํ™”์˜ ๋งˆ์ง€๋ง‰ ์กฐ๊ฐ์€ ์–ธ์ œ๋‚˜ ๋„‰๋„‰ํ•œ ์œ ํ†ต๊ธฐํ•œ(staleTime) ๋ณด์žฅ!

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

Q1. dehydrate์™€ HydrationBoundary๊ฐ€ ํ•จ๊ป˜ ํ•ด๊ฒฐํ•˜๋Š” ๋ฌธ์ œ๋Š” ๋ฌด์—‡์ธ๊ฐ€์š”?

โœ… ์ •๋‹ต: ์„œ๋ฒ„์—์„œ ๋ฏธ๋ฆฌ ์ฑ„์šด Query Cache ์ƒํƒœ๋ฅผ ์ง๋ ฌํ™”ํ•ด ํด๋ผ์ด์–ธํŠธ ์บ์‹œ์— ์ด์–ด ๋ถ™์—ฌ, ๊ฐ™์€ queryKey๋กœ ์ฆ‰์‹œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:
์„œ๋ฒ„๊ฐ€ HTML๋งŒ ๊ตฌ์›Œ๋„ ํด๋ผ์ด์–ธํŠธ Query Cache๊ฐ€ ๋น„์–ด ์žˆ์œผ๋ฉด ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์‹œ ๊ฐ€์ ธ์™€์•ผ ํ•ฉ๋‹ˆ๋‹ค. dehydrate(queryClient)๋Š” ์„œ๋ฒ„ ์บ์‹œ๋ฅผ ์ „๋‹ฌ ๊ฐ€๋Šฅํ•œ ํ˜•ํƒœ๋กœ ๋งŒ๋“ค๊ณ , HydrationBoundary๋Š” ํด๋ผ์ด์–ธํŠธ์—์„œ ๊ทธ ์ƒํƒœ๋ฅผ ๋ณต์›ํ•ฉ๋‹ˆ๋‹ค.

Q2. initialData props ์ „๋‹ฌ๋ณด๋‹ค Hydration ๋ฐฉ์‹์ด ์ปค์ง€๋Š” ์•ฑ์—์„œ ์œ ๋ฆฌํ•œ ์ด์œ ๋Š” ๋ฌด์—‡์ธ๊ฐ€์š”?

โœ… ์ •๋‹ต: ๊นŠ์€ ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ๋„ props drilling ์—†์ด ๋™์ผํ•œ queryKey๋งŒ์œผ๋กœ ํ”„๋ฆฌํŽ˜์น˜๋œ ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:
initialData๋Š” ๊ฐ€๊นŒ์šด ์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” ๊ฐ„๋‹จํ•˜์ง€๋งŒ ํŠธ๋ฆฌ๊ฐ€ ๊นŠ์–ด์งˆ์ˆ˜๋ก ์ „๋‹ฌ ๊ฒฝ๋กœ๊ฐ€ ๊ธธ์–ด์ง‘๋‹ˆ๋‹ค. Hydration์€ ๋ฐ์ดํ„ฐ๋ฅผ Query Cache์— ๋„ฃ์–ด๋‘๋ฏ€๋กœ, ์†Œ๋น„์ž๋Š” ์œ„์น˜์™€ ๊ด€๊ณ„์—†์ด ๊ฐ™์€ ํ‚ค๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์Šต๋‹ˆ๋‹ค. ์„œ๋ฒ„ ๋ฐ์ดํ„ฐ ๊ณ„์•ฝ์ด queryKey ์ค‘์‹ฌ์œผ๋กœ ์ •๋ฆฌ๋ฉ๋‹ˆ๋‹ค.

Q3. ์˜์ฒ ์ด์˜ ํ…Œ์ŠคํŠธ ํƒ€์ž„: SSR์—์„œ ์„œ๋ฒ„๊ฐ€ ๋‚ด๋ ค์ค€ ๊ธ€ ์ œ๋ชฉ๊ณผ ํด๋ผ์ด์–ธํŠธ ์ฒซ ๋ Œ๋”์˜ ๊ธ€ ์ œ๋ชฉ์ด ๋‹ฌ๋ผ hydration ๊ฒฝ๊ณ ๊ฐ€ ๋‚ฌ์Šต๋‹ˆ๋‹ค. ๊ฐ€์žฅ ๋จผ์ € ์˜์‹ฌํ•  ๊ฒƒ์€ ๋ฌด์—‡์ธ๊ฐ€์š”?

โœ… ์ •๋‹ต: ์„œ๋ฒ„ ํ”„๋ฆฌํŽ˜์น˜์™€ ํด๋ผ์ด์–ธํŠธ useQuery์˜ queryKey ๋˜๋Š” ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ ๊ธฐ์ค€์ด ์„œ๋กœ ๋‹ฌ๋ผ ์บ์‹œ๊ฐ€ ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋กœ ์—ฐ๊ฒฐ๋˜์ง€ ์•Š์€ ์ƒํ™ฉ์ž…๋‹ˆ๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:
Hydration์€ ์„œ๋ฒ„์™€ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๊ฐ™์€ ๋ฐ์ดํ„ฐ ๊ณ„์•ฝ์„ ๊ณต์œ ํ•  ๋•Œ ์•ˆ์ •์ ์ž…๋‹ˆ๋‹ค. ํ‚ค๊ฐ€ ์กฐ๊ธˆ๋งŒ ๋‹ฌ๋ผ๋„ ํด๋ผ์ด์–ธํŠธ๋Š” ๋นˆ ์บ์‹œ๋กœ ํŒ๋‹จํ•˜๊ณ  ์ƒˆ ์š”์ฒญ ๋˜๋Š” ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ๋ฅผ ๋ Œ๋”๋งํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜์ฒ ์ด ์ด์ œ SSR/hydration์„ โ€œHTML ๋ฌธ์ œโ€๊ฐ€ ์•„๋‹ˆ๋ผ โ€œ์บ์‹œ ๊ณ„์•ฝ ๋ฌธ์ œโ€๋กœ ๋ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

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

์™€, ๊ณผ๋„ํ•œ ๊ฑฐ ์•„๋‹ˆ์•ผ...? ๋‚œ ๋˜ ์–ด์ œ Next.js ํฌ๊ธฐํ•˜๊ณ  ์ „ํ†ต SPA๋กœ ๋Œ์•„๊ฐˆ ๋ป”ํ–ˆ๋Š”๋ฐ.
Props ๋กœ ๋„˜๊ธฐ๋ ค๋‹ค๊ฐ€ depth ๋„ˆ๋ฌด ๊ธธ์–ด์ ธ์„œ ์šธ๊ณ  ์žˆ์—ˆ๋Š”๋ฐ, ์„œ๋ฒ„์—์„œ ํ”„๋ฆฌํŽ˜์น˜(prefetchQuery) ๋ฌด๋ฆฌํ•˜๊ฒŒ ๋„ฃ๊ณ  Boundary๋กœ ๊ฐ์‹ธ์„œ ๋ธŒ๋ผ์šฐ์ € ์บ์‹œ์— ๊ณต์ค‘ ํˆฌํ•˜(AirDrop) ์‹œ์ผœ๋ฒ„๋ฆฌ๋Š” ๋ฐฉ์‹์€ ์ง„์งœ... ๊ธฐ์ˆ ์˜ ๋‚ญ๋งŒ ๊ทธ ์ž์ฒด๋‹ค.

๐Ÿ’ก ์˜ค๋Š˜์˜ ๊ตํ›ˆ: "์„œ๋ฒ„ ์ฃผ๋ฐฉ์—์„œ ๋ง›์žˆ๋Š” ์š”๋ฆฌ๋ฅผ ํ”ฝ์—…ํ–ˆ๋‹ค๋ฉด ํด๋ผ์ด์–ธํŠธ ์บ์‹œ ๊ธˆ๊ณ ์—๋„ ๊ทธ ๋ ˆ์‹œํ”ผ๋ฅผ ์ธ์ˆ˜์ธ๊ณ„(Hydration) ํ•ด์ฃผ์ž. ๊ทธ๋ž˜์•ผ ํ”„๋ก ํŠธ๊ฐ€ ๋ถˆํ•„์š”ํ•˜๊ฒŒ API๋ฅผ ๋˜ ์ค‘๋ณตํ˜ธ์ถœํ•˜๋Š” ๋ฌธ์ œ ์ƒํ™ฉ๋ฅผ ๋ง‰์„ ์ˆ˜ ์žˆ๋‹ค!"

๋“œ๋””์–ด Basic ๊ธฐ์ดˆ ๊ฐ€์ด๋“œ๋ผ์ธ 10๊ฐœ๊ฐ€ ๋๋‚ฌ๋‹ค. ์†”์งํžˆ ์ด 10๊ฐœ๋งŒ ์™„๋ฒฝํ•˜๊ฒŒ ์•Œ๊ณ  ์ฒด๋“ํ•ด๋„ ์–ด๋”” ๊ฐ€์„œ ์„œ๋ฒ„ ์ƒํƒœ์™€ ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ๋ฅผ ์„ž๋Š” ์‹ค์ˆ˜๋Š” ์ค„์ผ ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค. ์˜ํ˜ธ ๋ฆฌ๋“œ ๋‹˜์ด ๋‚ด์ผ๋ถ€ํ„ด ๋ฌดํ•œ ์Šคํฌ๋กค ๊ฐ™์€ Advanced ์˜์—ญ(์‹ฌํ™” 7๊ฐ•) ํ•  ๊ฑฐ๋ผ๋Š”๋ฐ, ์ผ๋‹จ ์˜ค๋Š˜๊นŒ์ง„ ์˜ค๋Š˜ ๋ฐฐ์šด ๊ธฐ์ค€์„ ๋…ธํŠธ์— ์ •๋ฆฌํ•˜๊ณ  ์‰ฌ์–ด์•ผ๊ฒ ๋‹ค