[Next.js] Hydration์ด๋ž€?

jinyยท2025๋…„ 11์›” 18์ผ

๊ธฐ์ˆ  ๋ฉด์ ‘

๋ชฉ๋ก ๋ณด๊ธฐ
73/78

๐Ÿ—ฃ๏ธ Hydration์ด๋ž€ ๋ฌด์—‡์ธ๊ฐ€์š”?

  • ์˜๋„: ์ง€์›์ž๊ฐ€ Hydration์˜ ๊ฐœ๋…๊ณผ ์ค‘์š”์„ฑ์„ ์ดํ•ดํ•˜๊ณ  ์žˆ๋Š”์ง€ ํ‰๊ฐ€
  • ํŒ
    • Hydration์˜ ๊ฐœ๋…์„ ์„ค๋ช…ํ•œ๋‹ค.
    • Hydration์ด ํ•„์š”ํ•œ ์ด์œ ๋ฅผ ์„ค๋ช…ํ•œ๋‹ค.
    • Hydration์˜ ๋™์ž‘ ๋ฐฉ์‹์„ ์„ค๋ช…ํ•œ๋‹ค.
  • ์ฃผ์–ด์ง„ ๋‹ต์•ˆ (๋ชจ๋ฒ” ๋‹ต์•ˆ)

    Hydration์€ ์„œ๋ฒ„์—์„œ ๋ Œ๋”๋ง๋œ HTML์„ ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ๋‹ค์‹œ ๋ Œ๋”๋งํ•˜์—ฌ ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” ๊ณผ์ •์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.
    Next.js์™€ ๊ฐ™์€ ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ๋Š” ์„œ๋ฒ„ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง(SSR)์„ ํ†ตํ•ด HTML์„ ๋ฏธ๋ฆฌ ์ƒ์„ฑํ•˜์—ฌ ๋น ๋ฅธ ์ดˆ๊ธฐ ๋กœ๋”ฉ ์†๋„๋ฅผ ์ œ๊ณตํ•˜๊ณ , SEO์—๋„ ์œ ๋ฆฌํ•œ ํ™˜๊ฒฝ์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
    ์ดํ›„ ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ๋Š” Hydration์„ ์ˆ˜ํ–‰ํ•˜์—ฌ React ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๊ณ , ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๋™์ ์ธ ์ธํ„ฐ๋ž™์…˜์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

    Hydration์ด ํ•„์š”ํ•œ ์ด์œ ๋Š” ์„œ๋ฒ„์—์„œ ๋ Œ๋”๋ง๋œ HTML๋งŒ์œผ๋กœ๋Š” ์‚ฌ์šฉ์ž ์ธํ„ฐ๋ ‰์…˜์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
    ์˜ˆ๋ฅผ ๋“ค์–ด, ์„œ๋ฒ„์—์„œ ์ƒ์„ฑ๋œ ๋ฒ„ํŠผ์ด ์žˆ๋‹ค๊ณ  ํ•ด๋„ Hydration์ด ์™„๋ฃŒ๋˜๊ธฐ ์ „๊นŒ์ง€๋Š” ํด๋ฆญ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
    React๋Š” ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์„œ๋ฒ„์—์„œ ๋ Œ๋”๋ง๋œ ์š”์†Œ์™€ ํด๋ผ์ด์–ธํŠธ์—์„œ ๋ Œ๋”๋ง๋œ ์š”์†Œ๋ฅผ ๋น„๊ต(React Reconciliation)ํ•œ ํ›„, ํ•„์š”ํ•œ ๋ถ€๋ถ„์„ ์—…๋ฐ์ดํŠธํ•˜์—ฌ UI๋ฅผ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.

    Hydration ๊ณผ์ •์—์„œ ์ฃผ์˜ํ•ด์•ผ ํ•  ์ ์€ ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„์—์„œ ์ƒ์„ฑ๋œ UI๊ฐ€ ๋™์ผํ•ด์•ผ ํ•œ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค.
    ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด React๊ฐ€ Hydration failed ์˜ค๋ฅ˜๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๊ฑฐ๋‚˜, ๋ถˆํ•„์š”ํ•œ ์ „์ฒด ๋ฆฌ๋ Œ๋”๋ง์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    ์ด๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด useEffect๋ฅผ ํ™œ์šฉํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ์—์„œ๋งŒ ์‹คํ–‰๋  ๋กœ์ง์„ ๋ถ„๋ฆฌํ•˜๊ฑฐ๋‚˜, suppressHydrationWarning์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    ๊ฒฐ๋ก ์ ์œผ๋กœ, Hydration์€ ์„œ๋ฒ„ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง์˜ ์ด์ ์„ ์œ ์ง€ํ•˜๋ฉด์„œ๋„ ํด๋ผ์ด์–ธํŠธ์—์„œ ๋™์  ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋Š” ํ•ต์‹ฌ์ ์ธ ๊ณผ์ •์ด๋ฉฐ, Next.js์—์„œ๋Š” ์ด๋ฅผ ์ตœ์ ํ™”ํ•˜๊ธฐ ์œ„ํ•ด useEffect, useHydration, next/dynamic ๋“ฑ์˜ ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


๐Ÿ“ ๊ฐœ๋… ์ •๋ฆฌ

๐ŸŒŸ Hydration์˜ ๊ฐœ๋…๊ณผ ํ•„์š”์„ฑ

  • ๊ฐœ๋…: ์ •์ ์ธ HTML์— ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋ถ™์—ฌ์„œ ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” React์˜ ๊ณผ์ •
  • Next.js์˜ ๊ธฐ๋ณธ ๋ Œ๋”๋ง ํ๋ฆ„์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.
    1. ์„œ๋ฒ„์—์„œ ๋ Œ๋”๋ง (SSR/SSG/ISR)
      • ์„œ๋ฒ„๊ฐ€ React ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‹คํ–‰ํ•ด์„œ ์ˆœ์ˆ˜ HTML์„ ๋งŒ๋“ ๋‹ค.
      • ์ด HTML์ด ๋ธŒ๋ผ์šฐ์ €์— ๋จผ์ € ๋„์ฐฉํ•ด์„œ, JS๊ฐ€ ์•„์ง ์•ˆ ๋ฐ›์•„์ ธ๋„ ์‚ฌ์šฉ์ž๋Š” ๋‚ด์šฉ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.
        โžก๏ธ FCP(First Contentful Paint)/LCP(Largest Contentful Paint) ๊ฐœ์„ 
    2. ํด๋ผ์ด์–ธํŠธ์—์„œ Hydration
      • ์ดํ›„ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ JS ๋ฒˆ๋“ค์„ ๋‹ค์šด๋กœ๋“œํ•˜๊ณ , ๊ฐ™์€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํด๋ผ์ด์–ธํŠธ์—์„œ ํ•œ ๋ฒˆ ๋” ๋ Œ๋”๋งํ•ด, ์ด๋ฏธ ์žˆ๋Š” HTML๊ณผ React ํŠธ๋ฆฌ๋ฅผ ์—ฐ๊ฒฐํ•˜๋Š” ๊ณผ์ •์ด ํ•„์š”ํ•˜๋‹ค.
        โžก๏ธ ์ด ๊ณผ์ •์ด Hydration!
      • Hydration์ด ๋๋‚˜์•ผ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ(onClick ๋“ฑ) ๋“ฑ๋ก, ์ƒํƒœ ์—…๋ฐ์ดํŠธ, ์ธํ„ฐ๋ž™์…˜์ด ๊ฐ€๋Šฅํ•ด์ง„๋‹ค.

    ๐Ÿ’ก ์ฆ‰, SSR/SSG/ISR๋Š” ๋ณด์—ฌ์ฃผ๋Š” ๋‹จ๊ณ„, Hydration์€ ์‚ด๋ฆฌ๋Š” ๋‹จ๊ณ„๋ผ๊ณ  ์ดํ•ดํ•˜๋ฉด ํŽธํ•˜๋‹ค.

  • ์™œ ํ•„์š”ํ•œ๊ฐ€?
    • SSR/SSG์˜ ์žฅ์ : HTML์„ ๋ฐ”๋กœ ๋ณด๋‚ด์„œ FCP/LCP๋ฅผ ๋น ๋ฅด๊ฒŒ ๋งŒ๋“ ๋‹ค.
    • CSR์˜ ์žฅ์ : ํ•œ ๋ฒˆ ์•ฑ์ด ๋œจ๊ณ  ๋‚˜๋ฉด SPA์ฒ˜๋Ÿผ ๋น ๋ฅธ ๋‚ด๋น„๊ฒŒ์ด์…˜, ํ’๋ถ€ํ•œ ์ƒํ˜ธ์ž‘์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.
    • Hydration SSR/SSG + CSR์˜ ๊ต์ฐจ์ : SSR๋กœ ๋น ๋ฅธ ํ™”๋ฉด ํ‘œ์‹œ + Hydration์œผ๋กœ CSR ์ˆ˜์ค€์˜ ์ƒํ˜ธ์ž‘์šฉ์„ ๋™์‹œ์— ์–ป๊ธฐ ์œ„ํ•œ ํ•„์ˆ˜ ๋‹จ๊ณ„์ด๋‹ค.

๐ŸŒŸ Next.js App Router์—์„œ Hydration์ด ์ผ์–ด๋‚˜๋Š” ์ „์ฒด ํ๋ฆ„

Next.js App Router ๊ธฐ์ค€์œผ๋กœ, ์ฒซ ๋กœ๋“œ ์‹œ ๋ Œ๋”๋ง ํŒŒ์ดํ”„๋ผ์ธ์„ ๋‹จ๊ณ„๋ณ„๋กœ ๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • ์„œ๋ฒ„์—์„œ ํ•˜๋Š” ์ผ
    1. Server Component ๋ Œ๋”๋ง
      • app/ ๋ฐ‘์˜ layout.tsx, page.tsx ๋“ฑ์€ ๊ธฐ๋ณธ์ ์œผ๋กœ Server Component์ด๋‹ค.
      • ์ด๋“ค์€ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์•„๋‹ˆ๋ผ ์„œ๋ฒ„์—์„œ ์‹คํ–‰๋˜๊ณ , ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ , JSX๋ฅผ ๋ Œ๋”๋งํ•œ๋‹ค.
    2. RSC Payload ์ƒ์„ฑ
      • ์„œ๋ฒ„์—์„œ React Server Component Payload(RSC Payload)๋ผ๋Š” ํŠน์ˆ˜ํ•œ ๋ฐ์ดํ„ฐ ํฌ๋งท์„ ๋งŒ๋“ ๋‹ค.
      • ์—ฌ๊ธฐ์—๋Š” ๋‹ค์Œ ๋‚ด์šฉ์ด ๋“ค์–ด๊ฐ„๋‹ค.
        • Server Component์˜ ๋ Œ๋” ๊ฒฐ๊ณผ
        • Client Component๊ฐ€ ๋“ค์–ด๊ฐˆ ์ž๋ฆฌ์™€ ๊ทธ JS ํŒŒ์ผ์— ๋Œ€ํ•œ ์ฐธ์กฐ
        • Server โ†’ Client๋กœ ์ „๋‹ฌ๋˜๋Š” props
    3. HTML ํ”„๋ฆฌ๋ Œ๋”๋ง
      • RSC Payload + Client Component ์ •๋ณด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ HTML์„ ๋ฏธ๋ฆฌ ๋ Œ๋”ํ•ด ํด๋ผ์ด์–ธํŠธ๋กœ ์ŠคํŠธ๋ฆฌ๋ฐ/์ „์†กํ•œ๋‹ค.
  • ํด๋ผ์ด์–ธํŠธ(๋ธŒ๋ผ์šฐ์ €)์—์„œ ํ•˜๋Š” ์ผ (Hydration ํฌํ•จ)

    1. HTML ํ‘œ์‹œ
      • ์„œ๋ฒ„์—์„œ ์˜จ HTML์„ ๋ฐ”๋กœ ํ™”๋ฉด์— ๊ทธ๋ฆฐ๋‹ค. โ†’ ๋น ๋ฅธ, ๋น„์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ํ”„๋ฆฌ๋ทฐ
    2. RSC Payload๋กœ ํŠธ๋ฆฌ ์žฌ๊ฒฐํ•ฉ
      • ๋ธŒ๋ผ์šฐ์ €์—์„œ React๊ฐ€ RSC Payload๋ฅผ ์‚ฌ์šฉํ•ด Server + Client Component ํŠธ๋ฆฌ๋ฅผ ์žฌ๊ตฌ์„ฑํ•œ๋‹ค.
    3. Client Component Hydration
      • ๋‹ค์šด๋กœ๋“œ๋œ JS ๋ฒˆ๋“ค์„ ์ด์šฉํ•ด Client Component๋“ค์— ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ/์ƒํƒœ๋ฅผ ๋ถ™์—ฌ์„œ ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒํ•˜๊ฒŒ ๋งŒ๋“ ๋‹ค.
        โžก๏ธ ์ด ๋‹จ๊ณ„๊ฐ€ ๋ฐ”๋กœ Hydration

    ๐Ÿ’ก ์—ฌ๊ธฐ์„œ ํ•ต์‹ฌ์€, Hydration์€ Server Component ์ „์ฒด๊ฐ€ ์•„๋‹ˆ๋ผ, Client Component ๋ถ€๋ถ„์„ ๋Œ€์ƒ์œผ๋กœ ํ•œ๋‹ค.

    • Server Component ๋ถ€๋ถ„์€ HTML๋งŒ ์žˆ๊ณ , JS ์ธํ„ฐ๋ž™์…˜ ์—†์Œ (Hydration ์—†์Œ)
    • Client Component๋กœ ๊ฐ์‹ผ ๋ถ€๋ถ„๋งŒ Hydration ๋น„์šฉ์ด ๋“ ๋‹ค๊ณ  ๋ณด๋ฉด ๋จ

๐ŸŒŸ Hydration๊ณผ Server/Client Components์˜ ๊ด€๊ณ„

  • ์–ด๋–ค ์ปดํฌ๋„ŒํŠธ๊ฐ€ Hydration ๋Œ€์ƒ์ธ๊ฐ€?
    • Server Component
      • ๊ธฐ๋ณธ๊ฐ’ (app/ ์•„๋ž˜ ๋Œ€๋ถ€๋ถ„)
      • SSR + ๋ฐ์ดํ„ฐ ํŒจ์นญ + ์บ์‹ฑ ๋‹ด๋‹น
      • ๋ธŒ๋ผ์šฐ์ €์— JS ๋ฒˆ๋“ค์ด ๊ฑฐ์˜ ๊ฐ€์ง€ ์•Š์Œ โžก๏ธ Hydration ๋ถˆํ•„์š”
    • Client Component ("use client")
      • ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ(onClick), ์ƒํƒœ(useState), useEffect, ๋ธŒ๋ผ์šฐ์ € API(window, localStorage) ๋“ฑ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ
      • ์ด ๋ถ€๋ถ„๋งŒ ๋ณ„๋„์˜ JS๋กœ ๋ฒˆ๋“ค๋˜์–ด ๋ธŒ๋ผ์šฐ์ €๋กœ ๋ณด๋‚ด์ง โžก๏ธ Hydration ๋Œ€์ƒ

      ๐Ÿ’ก ์ฆ‰, App Router์—์„œ๋Š” "Hydration ๋น„์šฉ = Client Component์˜ ์–‘๋งŒํผ"์ด๋ผ๊ณ  ์ดํ•ดํ•ด๋„ ๋œ๋‹ค.
      ๐Ÿ’ก ๊ทธ๋ž˜์„œ Next.js ๋ฌธ์„œ์—์„œ๋„ ๊ฐ€๋Šฅํ•œ Server Component๋ฅผ ๋งŽ์ด ์‚ฌ์šฉํ•ด JS ๋ฒˆ๋“ค์„ ์ค„์ด๊ณ , FCP๋ฅผ ๊ฐœ์„ ํ•˜๋ผ๊ณ  ๊ถŒ์žฅํ•œ๋‹ค.

  • ๊ฐ„๋‹จํ•œ ์˜ˆ์ œ

    • app/posts/[id]/page.tsx: Server Component

      import LikeButton from "./LikeButton";
      import { getPost } from "@/lib/data";
      
      export default async function Page({ params }: { params: { id: string } }) {
        const post = await getPost(params.id);
        
        return (
          <main>
            <h1>{post.title}</h1>
            {/* ์—ฌ๊ธฐ๊นŒ์ง€๋Š” Server Component โ†’ HTML๋งŒ ์ƒ์„ฑ */}
            <LikeButton initialLikes={post.likes} />
          </main>
        );
      }
    • app/posts/[id]/LikeButton.tsx: Client Component

      "use client";
      
      import { useState } from "react";
      
      export default function LikeButton({ initialLikes }: { initialLikes: number }) {
        const [likes, setLikes] = useState(initialLikes);
        return (
          <button onClick={() => setLikes((v) => v + 1)}>
            ์ข‹์•„์š” {likes}
          </button>
        );
      }
    • ์„œ๋ฒ„์—์„œ๋Š” LikeButton๊นŒ์ง€ ํฌํ•จ๋œ HTML์„ ํ”„๋ฆฌ๋ Œ๋”ํ•œ๋‹ค.
    • ํด๋ผ์ด์–ธํŠธ์—์„œ๋Š” LikeButton์— ๋Œ€ํ•œ JS๊ฐ€ ๋‹ค์šด๋กœ๋“œ๋œ ํ›„, ์ด ๋ฒ„ํŠผ DOM์— onClick๊ณผ state๋ฅผ ์—ฐ๊ฒฐํ•˜๋Š” Hydration์ด ์ˆ˜ํ–‰๋œ๋‹ค.

๐ŸŒŸ React 18, Streaming SSR, Selective Hydration๊ณผ Next.js

์˜ˆ์ „(React 17๊นŒ์ง€)์˜ SSR์€ ๊ฑฐ์˜ "์˜ฌ-์˜ค์–ด-๋‚ซ์‹ฑ(all-or-nothing) Hydration"์ด์—ˆ๋‹ค.

  • ๋ชจ๋“  ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ โ†’ ์ „์ฒด HTML ์ƒ์„ฑ โ†’ ์ „์ฒด JS ๋ฒˆ๋“ค ๋‹ค์šด๋กœ๋“œ โ†’ ์ „์ฒด ํŠธ๋ฆฌ๋ฅผ ํ•œ ๋ฒˆ์— Hydration
  • ์ด ๋ฐฉ์‹์˜ ๋ฌธ์ œ: JS๊ฐ€ ๋ฌด๊ฑฐ์›Œ์งˆ์ˆ˜๋ก TTI(Time To Interactive), TBT(Total Blocking Time) ๊ฐ™์€ ์„ฑ๋Šฅ ์ง€ํ‘œ๊ฐ€ ๋‚˜๋น ์ง„๋‹ค.
  1. Streaming SSR
    React 18 + Next.js๋Š” Streaming SSR์„ ๋„์ž…ํ–ˆ๋‹ค.
    • ์„œ๋ฒ„๊ฐ€ HTML์„ ํ•œ ๋ฒˆ์— ๋‹ค ๋ณด๋‚ด๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ, ๋น ๋ฅด๊ฒŒ ์ค€๋น„๋œ ๋ถ€๋ถ„๋ถ€ํ„ฐ chunk ๋‹จ์œ„๋กœ ์ŠคํŠธ๋ฆฌ๋ฐํ•œ๋‹ค.
    • ์‚ฌ์šฉ์ž๋Š” ํŽ˜์ด์ง€ ์ผ๋ถ€๋ผ๋„ ๋จผ์ € ๋ณผ ์ˆ˜ ์žˆ๋‹ค.
    • Next.js์—์„œ loading.js ํŒŒ์ผ์„ ์“ฐ๋ฉด, Suspense๋ฅผ ํ†ตํ•ด ์ด ์ŠคํŠธ๋ฆฌ๋ฐ ๊ฒฝํ—˜์„ ๋” ์‰ฝ๊ฒŒ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.
  1. Selective Hydration
    React 18์˜ ํ•ต์‹ฌ ๊ธฐ๋Šฅ ์ค‘ ํ•˜๋‚˜๊ฐ€ Selective Hydration์ด๋‹ค.
    • ์˜ˆ์ „: ํ•œ ๋ฒˆ์— ์ „์ฒด ํŽ˜์ด์ง€๋ฅผ Hydration โ†’ ์ธํ„ฐ๋ž™์…˜ ๊ฐ€๋Šฅ
    • ์ง€๊ธˆ:
      • HTML์ด ์ŠคํŠธ๋ฆฌ๋ฐ๋˜๋Š” ๋™์‹œ์—,
      • React๊ฐ€ ์ผ๋ถ€ ์ปดํฌ๋„ŒํŠธ๋งŒ ์šฐ์„  Hydration ํ•  ์ˆ˜ ์žˆ๋‹ค.
      • ํŠนํžˆ ์‚ฌ์šฉ์ž๊ฐ€ ๋จผ์ € ์ƒํ˜ธ์ž‘์šฉํ•˜๋ ค๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ์šฐ์„ ์ ์œผ๋กœ Hydrationํ•œ๋‹ค.
  • ์˜ˆ์‹œ ํ๋ฆ„
    1. ์ƒ๋‹จ Hero ์„น์…˜, ๋„ค๋น„๊ฒŒ์ด์…˜ ๊ฐ™์€ ๊ฐ€๋ฒผ์šด ๋ถ€๋ถ„ โ†’ ๋จผ์ € ์ŠคํŠธ๋ฆฌ๋ฐ + Hydration
    2. ์•„๋ž˜ ๋ฌด๊ฑฐ์šด ์ฐจํŠธ, ๋Œ€์‹œ๋ณด๋“œ โ†’ JS๊ฐ€ ์ค€๋น„๋œ ํ›„์— ์ฒœ์ฒœํžˆ Hydration
    3. ์‚ฌ์šฉ์ž๊ฐ€ ์•„๋ž˜ ์ฐจํŠธ๋ฅผ ์Šคํฌ๋กคํ•ด ํด๋ฆญํ•˜๋ฉด โ†’ React๊ฐ€ ๊ทธ ๋ถ€๋ถ„ Hydration์„ ์šฐ์„ ์ˆœ์œ„๋กœ ์˜ฌ๋ ค์„œ ์ฒ˜๋ฆฌ

๐Ÿ’ก Next.js App Router์—์„œ๋Š” Streaming SSR + Selective Hydration + Server Components๋ฅผ ์กฐํ•ฉํ•ด์„œ "๋นจ๋ฆฌ ๋ณด์ด๊ณ , ํ•„์š”ํ•œ ๊ณณ๋งŒ ๋จผ์ € ๋ฐ˜์‘ํ•˜๋Š”" ๋ Œ๋”๋ง/ํ•˜์ด๋“œ๋ ˆ์ด์…˜ ์ „๋žต์„ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ๋‹ค.


๐ŸŒŸ Hydration ์—๋Ÿฌ(๋ถˆ์ผ์น˜)์™€ ์›์ธ

Hydration์—์„œ ๊ฐ€์žฅ ์ž์ฃผ ๋ณด๋Š” ๊ฒŒ ๋ฐ”๋กœ Hydration mismatch ๊ฒฝ๊ณ /์—๋Ÿฌ์ด๋‹ค.

  • Hydration mismatch๋ž€?
    • ์„œ๋ฒ„์—์„œ ๋งŒ๋“  HTML๊ณผ ํด๋ผ์ด์–ธํŠธ์—์„œ ๊ฐ™์€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‹ค์‹œ ๋ Œ๋”๋งํ•œ ๊ฒฐ๊ณผ๊ฐ€ ์กฐ๊ธˆ์ด๋ผ๋„ ๋‹ค๋ฅด๋ฉด React๊ฐ€ ๊ฒฝ๊ณ ๋ฅผ ๋„์šด๋‹ค. (ํ…์ŠคํŠธ, ํƒœ๊ทธ ๊ตฌ์กฐ ๋“ฑ)
    • ์˜ˆ: ์„œ๋ฒ„์—์„œ <span>1</span>์„ ๋ณด๋ƒˆ๋Š”๋ฐ, ํด๋ผ์ด์–ธํŠธ์—์„œ๋Š” <span>2</span>์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜๋Š” ์ƒํ™ฉ
  • Hydration mismatch์˜ ๋Œ€ํ‘œ์ ์ธ ์›์ธ
    ๋Œ€๋ถ€๋ถ„์€ ์„œ๋ฒ„/ํด๋ผ์ด์–ธํŠธ์—์„œ ๋‹ค๋ฅธ ๊ฐ’์œผ๋กœ ๋ Œ๋”๋ง๋˜๋Š” ์ฝ”๋“œ ๋•Œ๋ฌธ์— ์ƒ๊ธด๋‹ค.
    1. ๋ธŒ๋ผ์šฐ์ € ์ „์šฉ API ์‚ฌ์šฉ
      • window, document, localStorage, navigator ๋“ฑ
      • ์„œ๋ฒ„ ๋ Œ๋”๋ง ์‹œ์—๋Š” ์กด์žฌํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๋ถ„๊ธฐ ์ฒ˜๋ฆฌ ์—†์œผ๋ฉด ์—๋Ÿฌ/๋ถˆ์ผ์น˜
      • Next.js๋„ ์ด๋Ÿฐ ๋ฌธ์ œ๋ฅผ ํ™˜๊ฒฝ ์˜ค์—ผ(environment poisoning)์ด๋ผ๊ณ  ๋ถ€๋ฅด๋ฉฐ, Server Component์—์„œ ๋ธŒ๋ผ์šฐ์ € API๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•˜์ง€ ๋ง๋ผ๊ณ  ๊ฒฝ๊ณ ํ•œ๋‹ค.
    2. ๋žœ๋ค ๊ฐ’/ํ˜„์žฌ ์‹œ๊ฐ„์— ์˜์กดํ•˜๋Š” ๋ Œ๋”๋ง
      • Math.random(), Date.now(), new Date() ๋“ฑ์„ ๋ฐ”๋กœ JSX์— ์‚ฌ์šฉํ•˜๋ฉด, ์„œ๋ฒ„ ๋ Œ๋”๋ง๊ณผ ํด๋ผ์ด์–ธํŠธ ๋ Œ๋”๋ง ๊ฒฐ๊ณผ๊ฐ€ ๊ฑฐ์˜ ํ•ญ์ƒ ๋‹ค๋ฅด๋‹ค.
    3. ์กฐ๊ฑด๋ถ€ ๋ Œ๋”๋ง์ด ํด๋ผ์ด์–ธํŠธ์—์„œ๋งŒ ๋‹ค๋ฅธ ๊ฒฝ์šฐ
      • ์˜ˆ: ๋กœ๊ทธ์ธ ์—ฌ๋ถ€๋ฅผ SSR ์‹œ์  ์ •๋ณด๊ฐ€ ์•„๋‹ˆ๋ผ, localStorage์—์„œ๋งŒ ์ฝ์–ด์„œ ๊ฒฐ์ •ํ•˜๋Š” UI
        • ์„œ๋ฒ„: ๋กœ์ปฌ์Šคํ† ๋ฆฌ์ง€ ์—†์Œ โ†’ A ๋ฒ„์ „์œผ๋กœ ๋ Œ๋”
        • ํด๋ผ์ด์–ธํŠธ: ๋กœ์ปฌ์Šคํ† ๋ฆฌ์ง€ ์žˆ์Œ โ†’ B ๋ฒ„์ „์œผ๋กœ ๋ Œ๋” โžก๏ธ mismatch
    4. ์™ธ๋ถ€ UI ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ์Šคํƒ€์ผ/๊ตฌ์กฐ ์ฐจ์ด
      • MUI, Chakra ๋“ฑ ์ผ๋ถ€ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ธŒ๋ผ์šฐ์ € ํ™˜๊ฒฝ์—์„œ๋งŒ ์Šคํƒ€์ผ ๊ณ„์‚ฐ โ†’ SSR ๊ฒฐ๊ณผ์™€ ๋งž์ง€ ์•Š์„ ์ˆ˜ ์žˆ๋‹ค.
  • Next.js์—์„œ์˜ ๋Œ€ํ‘œ์ ์ธ ํ•ด๊ฒฐ ํŒจํ„ด

    1. ๋ธŒ๋ผ์šฐ์ € ์ „์šฉ ๋กœ์ง์€ Client Component ์•ˆ์—์„œ๋งŒ

      "use client";
      
      import { useEffect, useState } from "react";
      
      export default function ClientOnly() {
        const [mounted, setMounted] = useState(false);
        
        useEffect(() => {
          setMounted(true);
        }, []);
        
        if (!mounted) {
          // ์„œ๋ฒ„์™€ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋™์ผํ•˜๊ฒŒ ๋ Œ๋”๋˜๋„๋ก
          // ์ดˆ๊ธฐ์—๋Š” ์•„๋ฌด๊ฒƒ๋„ ์•ˆ ๋ณด์—ฌ์ฃผ๊ฑฐ๋‚˜ skeleton๋งŒ ๋ณด์—ฌ์คŒ
          return null;
        }
        
        return <div>๋ธŒ๋ผ์šฐ์ € ์ „์šฉ ์ •๋ณด: {window.innerWidth}</div>;
      }
      • "use client" ํŒŒ์ผ ์•ˆ์œผ๋กœ ๋ถ„๋ฆฌํ•˜๊ณ ,
      • ๊ฑฐ๊ธฐ์—์„œ useEffect, useState๋กœ ๋งˆ์šดํŠธ ์ดํ›„์— ๋ธŒ๋ผ์šฐ์ € ๊ฐ’์„ ์ฝ๋Š” ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•œ๋‹ค.
    2. ์„œ๋ฒ„/ํด๋ผ์ด์–ธํŠธ ๋ Œ๋” ๊ฒฐ๊ณผ๋ฅผ ๊ฐ™๊ฒŒ ์œ ์ง€

      • ์ดˆ๊ธฐ ๋ Œ๋”์—์„œ๋Š” ์„œ๋ฒ„์™€ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋™์ผํ•œ ๊ฐ’์„ ์‚ฌ์šฉํ•˜๋„๋ก ๋งŒ๋“ค๊ณ ,
      • ๊ฐ’ ์ฐจ์ด๋Š” useEffect ์ดํ›„์— ๋ฐ˜์˜ํ•œ๋‹ค. (์˜ˆ: ์‹ค์ œ ์‹œ๊ฐ„ ์—…๋ฐ์ดํŠธ)
    3. ์ •๋ง ์–ด์ฉ” ์ˆ˜ ์—†์„ ๋•Œ suppressHydrationWarning

      <div suppressHydrationWarning>
        {isClient ? "ํด๋ผ์ด์–ธํŠธ ๊ฐ’" : "์„œ๋ฒ„ ๊ฐ’"}
      </div>
      • ์ผ๋ถ€ ํ…์ŠคํŠธ๋งŒ ์˜๋„์ ์œผ๋กœ ๋‹ค๋ฅผ ์ˆ˜๋ฐ–์— ์—†์„ ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.
      • ํ•˜์ง€๋งŒ ๊ทผ๋ณธ ํ•ด๊ฒฐ์ฑ…์€ ์•„๋‹ˆ๊ณ , ๋งˆ์ง€๋ง‰ ์ˆ˜๋‹จ์— ๊ฐ€๊น๋‹ค.
    4. Client-only ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ๋™์  import + ssr: false

      • ์˜ˆ: window์— ๊ฐ•ํ•˜๊ฒŒ ์˜์กดํ•˜๋Š” ์ฐจํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
      • Next.js์—์„œ dynamic(() => import('./Chart'), { ssr: false }) ๋ฐฉ์‹์œผ๋กœ ๋ Œ๋”๋ง์„ ํด๋ผ์ด์–ธํŠธ์—์„œ๋งŒ ํ•˜๊ฒŒ ํ•ด์„œ mismatch๋ฅผ ์ œ๊ฑฐํ•œ๋‹ค.

๐ŸŒŸ Next.js์—์„œ Hydration ๊ด€๋ จ ์‹ค๋ฌด ์ฒดํฌ๋ฆฌ์ŠคํŠธ

  1. Server vs. Client ์ปดํฌ๋„ŒํŠธ ์„ค๊ณ„
    • ๊ธฐ๋ณธ์€ Server Component: ๋ฐ์ดํ„ฐ ํŒจ์นญ, SEO ์ค‘์š”ํ•œ ์ฝ˜ํ…์ธ , ์ •์  ํ…์ŠคํŠธ
    • ์ •๋ง ํ•„์š”ํ•œ ๊ณณ๋งŒ Client Component: ๋ฒ„ํŠผ ํด๋ฆญ, ํผ ์ž…๋ ฅ, ํ† ๊ธ€ UI, ๋ชจ๋‹ฌ์ฒ˜๋Ÿผ ์ƒํ˜ธ์ž‘์šฉ์ด ์žˆ๋Š” ๋ถ€๋ถ„
    • ์ƒํ˜ธ์ž‘์šฉ์ด ์—†๋Š” ์ปดํฌ๋„ŒํŠธ๋Š” ๋ฌด์กฐ๊ฑด Server Component๊ฐ€ ๋””ํดํŠธ๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด Hydration ๋น„์šฉ์ด ์ž๋™์œผ๋กœ ์ค„์–ด๋“ ๋‹ค.
  1. ๋ธŒ๋ผ์šฐ์ € ์˜์กด ๋กœ์ง ๋‹ค๋ฃจ๊ธฐ
    • window, document, localStorage ๋“ฑ์€
      • Server Component์—์„œ ์ง์ ‘ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค.
      • "use client" ์ปดํฌ๋„ŒํŠธ + useEffect ์•ˆ์—์„œ๋งŒ ์‚ฌ์šฉํ•œ๋‹ค.
    • ์‹œ๊ฐ„/๋žœ๋ค ๊ฐ’
      • ์„œ๋ฒ„/ํด๋ผ์ด์–ธํŠธ์— ๋”ฐ๋ผ ๋ณ€ํ•˜์ง€ ์•Š๋Š” ๊ฐ’(์˜ˆ: DB์—์„œ ์˜จ timestamp)์„ ์šฐ์„  ์‚ฌ์šฉํ•œ๋‹ค.
      • ํ˜„์žฌ ์‹œ๊ฐ„์ฒ˜๋Ÿผ ๊ณ„์† ๋ณ€ํ•˜๋Š” ๊ฐ’์€ ์ดˆ๊ธฐ ๋ Œ๋”๋ฅผ ์„œ๋ฒ„ ๊ธฐ์ค€์œผ๋กœ ๋งž์ถ”๊ณ , ๋งˆ์šดํŠธ ํ›„ ํด๋ผ์ด์–ธํŠธ์—์„œ ์—…๋ฐ์ดํŠธํ•œ๋‹ค.
  1. Heavy ์ปดํฌ๋„ŒํŠธ ์ตœ์ ํ™”
    • ์ฐจํŠธ, ์—๋””ํ„ฐ, ์ง€๋„ ๋“ฑ ๋ฌด๊ฑฐ์šด ์ปดํฌ๋„ŒํŠธ๋Š”
      • ๋™์  import + Suspense๋กœ Hydration ์‹œ์ ์„ ์กฐ์ ˆํ•œ๋‹ค.
      • ํ•„์š”ํ•˜๋‹ค๋ฉด ssr: false๋กœ ์™„์ „ํžˆ ํด๋ผ์ด์–ธํŠธ๋ฅผ ๋ Œ๋”๋งํ•œ๋‹ค.
    • ํ™”๋ฉด์— ๋ณด์ด๋Š” ์ˆœ๊ฐ„๊นŒ์ง€๋งŒ Hydrationํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด
      • IntersectionObserver๋‚˜ "์Šคํฌ๋กค ์‹œ์ ์— ๋กœ๋”ฉ"๊ฐ™์€ ํŒจํ„ด์„ ์‚ฌ์šฉํ•œ๋‹ค.
  1. Hydration mismatch ๋””๋ฒ„๊น… ์Šต๊ด€
    ๊ฒฝ๊ณ  ๋ฉ”์‹œ์ง€(ํ…์ŠคํŠธ/DOM ๋ถˆ์ผ์น˜)๋ฅผ ๋ณด๋ฉด
    • ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ๊ฐ€ Server์ธ์ง€ Client์ธ์ง€ ํ™•์ธํ•œ๋‹ค.
    • ์ดˆ๊ธฐ ๋ Œ๋”์— ๋žœ๋ค/์‹œ๊ฐ„/๋ธŒ๋ผ์šฐ์ € API๊ฐ€ ๊ฐœ์ž…ํ•˜๋Š”์ง€ ํ™•์ธํ•œ๋‹ค.
    • ํ•„์š”ํ•˜๋‹ค๋ฉด
      • ํด๋ผ์ด์–ธํŠธ ์ „์šฉ์œผ๋กœ ๋ถ„๋ฆฌ
      • ์ดˆ๊ธฐ ๋ Œ๋” ๊ฐ’์„ ๊ณ ์ •
      • suppressHydrationWarning์€ ๋งˆ์ง€๋ง‰ ์ˆ˜๋‹จ์œผ๋กœ๋งŒ ์‚ฌ์šฉํ•œ๋‹ค.

0๊ฐœ์˜ ๋Œ“๊ธ€