๐Ÿš€ 11. Next.js App Router + Jotai SSR ์ „๋žต

๐Ÿ“‹ ๊ฐœ์š”

SSR์—์„œ Provider ํ•„์ˆ˜ ์ด์œ , useHydrateAtoms๋กœ ์„œ๋ฒ„ ๋ฐ์ดํ„ฐ ์ฃผ์ž…, Serverโ†’Client Component ๋ฐ์ดํ„ฐ ์ „๋‹ฌ, SWC ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๋ฐฐ์›๋‹ˆ๋‹ค.

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • SSR ํ™˜๊ฒฝ์—์„œ Provider ๊ฐ€ ์™œ ํ•„์ˆ˜์ด๋ฉฐ, ์—†์œผ๋ฉด ์–ด๋–ค ๋ณด์•ˆ ์‚ฌ๊ณ ๊ฐ€ ๋‚˜๋Š”์ง€ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • useHydrateAtoms ๋กœ Server Component ์—์„œ fetch ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ Client Component ์˜ atom ์— ์ฃผ์ž…ํ•˜๋Š” ํŒจํ„ด์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • SWC ํ”Œ๋Ÿฌ๊ทธ์ธ ์„ค์ •์œผ๋กœ DX ๋ฅผ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ“‹ ๋ชฉ์ฐจ


๐Ÿ“Œ ์ด ๋ฌธ์„œ๋ฅผ ์ฝ๊ธฐ ์ „์—

โฑ๏ธ ์˜ˆ์ƒ ์ฝ๊ธฐ ์‹œ๊ฐ„: 35๋ถ„ (์ „์ฒด) / ํ•ต์‹ฌ ํŒŒํŠธ๋งŒ: 20๋ถ„

๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ๋ฐฐ๊ฒฝ ์„ธ๊ณ„๊ด€: ์ตœ์•…์˜ ๋ฐฐํฌ ๋‚ 

์˜์ˆ˜๋„ค ํŒ€์ด ์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ ์•ฑ์„ ์ฒ˜์Œ์œผ๋กœ ์‹ค์„œ๋ฒ„์— ๋ฐฐํฌํ•œ ๋‚ ์ด์•ผ. ๋ก ์นญ 10๋ถ„ ๋งŒ์— ์˜์ˆ˜ ๋‹˜์˜ ์ „ํ™”๊ฐ€ ์šธ๋ ธ์–ด:

  • ๐Ÿ‘” ์˜์ˆ˜ ๋‹˜ (ํŒจ๋‹‰): "์•ผ, ์ง€๊ธˆ ์‚ฌ์šฉ์ž๋“ค์ด ๋‹ค๋ฅธ ์‚ฌ๋žŒ ์ด๋ฆ„์ด๋ž‘ ์ด๋ฉ”์ผ์ด ๋ณด์ธ๋‹ค๊ณ  ๋‚œ๋ฆฌ๋‹ค! ๋‚ด ๊ณ„์ •์œผ๋กœ ๋“ค์–ด๊ฐ”๋”๋‹ˆ ์˜ํ˜ธ ์ด๋ฆ„์ด ๋œฌ๋‹ค๊ณ !"
  • ๐Ÿฆ ์˜ํ˜ธ ๋‹˜ (์นจ์ฐฉํ•˜๊ฒŒ ์ฝ”๋“œ ์—ด์–ด๋ณด๋ฉฐ): "์˜์ฒ  ๋‹˜, layout.tsx ์— Provider ์žˆ์–ด์š”?"
  • ๐Ÿฃ ์˜์ฒ : (์† ๋–จ๋ฉด์„œ) "Provider ์š”? ๋กœ์ปฌ์—์„œ ์ž˜ ๋๋Š”๋ฐ... ์•„, ์—†์–ด์š”."
  • ๐Ÿฆ ์˜ํ˜ธ ๋‹˜: "์ง€๊ธˆ ๋ฐ”๋กœ ๋กค๋ฐฑํ•˜๊ณ  Provider ์ถ”๊ฐ€ํ•ด์„œ ์žฌ๋ฐฐํฌํ•ด์š”. 5๋ถ„ ๊ฑธ๋ ค์š”."
  • ๐Ÿ‘” ์˜์ˆ˜ ๋‹˜: "์™œ ์ด๋Ÿฐ ์ผ์ด ์ƒ๊ธด ๊ฑฐ์•ผ?"
  • ๐Ÿฆ ์˜ํ˜ธ ๋‹˜: "์„œ๋ฒ„์—์„œ ๋ชจ๋“  ์š”์ฒญ์ด ์ „์—ญ store ํ•˜๋‚˜๋ฅผ ๊ณต์œ ํ•ด์„œ์š”. A ์œ ์ €๊ฐ€ ์“ด store ๋ฅผ B ์œ ์ €๊ฐ€ ์ฝ์€ ๊ฑฐ์˜ˆ์š”. ์˜ค๋Š˜๋ถ€ํ„ฐ ์ด ์›์ธ์„ ์ œ๋Œ€๋กœ ์ดํ•ดํ•˜๊ณ  ๋„˜์–ด๊ฐ€์•ผ ํ•ด์š”."

๐Ÿšจ ์‹ค์ œ ๋ณด์•ˆ ์‚ฌ๊ณ  โ€” Provider ์—†์ด ๋ฐฐํฌํ•œ ๋‚  ๐Ÿ”ด

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • ์ „์—ญ ์‹ฑ๊ธ€ํ„ด store ๊ฐ€ ์„œ๋ฒ„์—์„œ ์™œ ์œ„ํ—˜ํ•œ์ง€ ๊ตฌ์ฒด์ ์ธ ํƒ€์ž„๋ผ์ธ์œผ๋กœ ์ดํ•ดํ•œ๋‹ค
์‹œ๊ฐ„ 00:00.000 โ€” ์˜์ฒ ์ด ์„ธ์…˜ ์š”์ฒญ ๋„์ฐฉ
  โ†’ ์„œ๋ฒ„๊ฐ€ userAtom ์— { name: '์˜์ฒ ', email: 'youngcheol@email.com' } ๋ฅผ set
  โ†’ HTML ๋ Œ๋”๋ง ์‹œ์ž‘

์‹œ๊ฐ„ 00:00.050 โ€” ์˜์ˆ˜ ์„ธ์…˜ ์š”์ฒญ ๋„์ฐฉ (๋ Œ๋” ์™„๋ฃŒ ์ „!)
  โ†’ ์„œ๋ฒ„๊ฐ€ userAtom.get() ํ˜ธ์ถœ
  โ†’ ์ „์—ญ store ์—๋Š” ์•„์ง '์˜์ฒ ' ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์Œ
  โ†’ ์˜์ˆ˜์—๊ฒŒ ์˜์ฒ ์˜ ์ด๋ฆ„, ์ด๋ฉ”์ผ์ด ํฌํ•จ๋œ HTML ์ „์†ก ๐Ÿ’€

์‹œ๊ฐ„ 00:00.100 โ€” ์˜์ฒ ์ด ์š”์ฒญ ์™„๋ฃŒ
  โ†’ ์ด๋ฏธ ๋Šฆ์—ˆ์Œ

์ด๊ฒŒ Race Condition + ์ „์—ญ ์ƒํƒœ ๊ณต์œ ๊ฐ€ ๋งŒ๋“ค์–ด๋‚ด๋Š” ์ตœ์•…์˜ ์‹œ๋‚˜๋ฆฌ์˜ค์•ผ. ๋ณด์•ˆ ๊ฐ์‚ฌ์—์„œ "๊ฐœ์ธ์ •๋ณด ๋ฌด๋‹จ ๋…ธ์ถœ" ๋กœ ๋ถ„๋ฅ˜๋  ์ˆ˜ ์žˆ๋Š” ์‚ฌ๊ณ ์•ผ.

// โŒ ์‚ฌ๊ณ ์˜ ์›์ธ ์ฝ”๋“œ โ€” Provider ์—†๋Š” layout.tsx
 
// app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ko">
      <body>
        {children} {/* โ† ์ „์—ญ ์‹ฑ๊ธ€ํ„ด store ๊ฐ€ ๋ชจ๋“  ์š”์ฒญ์— ๊ณต์œ ๋จ */}
      </body>
    </html>
  )
}
 
// atoms/user.ts
// ๐Ÿฃ ์˜์ฒ : "๋ชจ๋“ˆ ์ตœ์ƒ๋‹จ์— atom ์„ ์–ธํ–ˆ๋Š”๋ฐ, ์ด๊ฒŒ ์„œ๋ฒ„ ์ „์—ญ ๋ณ€์ˆ˜๊ฐ€ ๋ผ๋ฒ„๋ฆฌ๋Š” ๊ฑฐ์˜€์–ด์š”"
export const userAtom = atom<User | null>(null)
// โ†’ ์ด atom ์˜ ๊ฐ’์ด ์„œ๋ฒ„ ์ธ์Šคํ„ด์Šค์˜ ์ „์—ญ store ์— ์ €์žฅ๋จ
// โ†’ ์š”์ฒญ ๊ฐ„ ๊ณต์œ  โ†’ ๋ณด์•ˆ ์‚ฌ๊ณ 

๐Ÿ”’ SSR ์—์„œ Provider ๊ฐ€ ํ•„์ˆ˜์ธ ์ด์œ  ๐Ÿ”ด

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • Node.js ์˜ ๋ชจ๋“ˆ ์บ์‹ฑ์ด ์™œ ์ด ๋ฌธ์ œ๋ฅผ ์ผ์œผํ‚ค๋Š”์ง€ ์ดํ•ดํ•œ๋‹ค
  • Provider ๊ฐ€ ์–ด๋–ป๊ฒŒ ์š”์ฒญ ๋‹จ์œ„์˜ store ๊ฒฉ๋ฆฌ๋ฅผ ๋งŒ๋“œ๋Š”์ง€ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค
Node.js ์„œ๋ฒ„์˜ ํŠน์„ฑ:
  - ๋ชจ๋“ˆ์€ ํ•œ ๋ฒˆ๋งŒ ๋กœ๋“œ๋˜๊ณ  ์บ์‹ฑ๋จ
  - atom config (์„ค๊ณ„๋„)๋Š” ๋ชจ๋“ˆ ์ตœ์ƒ๋‹จ์—์„œ ํ•œ ๋ฒˆ ์ƒ์„ฑ
  - Provider-less ๋ชจ๋“œ์˜ ์ „์—ญ store ๋„ ๋ชจ๋“ˆ ๋ ˆ๋ฒจ์—์„œ ํ•œ ๋ฒˆ ์ƒ์„ฑ
  โ†’ ๋ชจ๋“  HTTP ์š”์ฒญ์ด ๊ฐ™์€ store ๋ฅผ ๊ณต์œ !

Provider ์˜ ๋™์ž‘:
  - Provider ์ปดํฌ๋„ŒํŠธ๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ createStore() ๋ฅผ ํ˜ธ์ถœํ•ด์„œ ์ƒˆ store ๋ฅผ ๋งŒ๋“ฆ
  - React ๋ Œ๋”๋ง ์‹œ Provider ๊ฐ€ ์ƒˆ store ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑ
  - SSR ์—์„œ๋Š” ์š”์ฒญ๋งˆ๋‹ค React ๊ฐ€ ๋ Œ๋”๋ง๋˜๋ฏ€๋กœ โ†’ ์š”์ฒญ๋งˆ๋‹ค ์ƒˆ store ์ƒ์„ฑ
  โ†’ ์š”์ฒญ ๊ฐ„ ์™„์ „ํ•œ ๊ฒฉ๋ฆฌ!
// โœ… ํ•ด๊ฒฐ๋œ ์ƒํƒœ โ€” Provider ๊ฐ€ ์š”์ฒญ๋งˆ๋‹ค ์ƒˆ store ๋ฅผ ๊ฒฉ๋ฆฌ
 
// ์š”์ฒญ 1 (์˜์ฒ  ์„ธ์…˜): Provider ๋ Œ๋” โ†’ createStore() โ†’ store A ์ƒ์„ฑ
//                     userAtom in store A = { name: '์˜์ฒ ' }
 
// ์š”์ฒญ 2 (์˜์ˆ˜ ์„ธ์…˜): Provider ๋ Œ๋” โ†’ createStore() โ†’ store B ์ƒ์„ฑ
//                     userAtom in store B = { name: '์˜์ˆ˜' }
 
// store A ์™€ store B ๋Š” ์™„์ „ํžˆ ๋…๋ฆฝ โ†’ ๋ฐ์ดํ„ฐ ๊ฒฉ๋ฆฌ โœ…

๐Ÿ—๏ธ layout.tsx Provider ์„ค์ • ํŒจํ„ด ๐ŸŸก

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • App Router ์—์„œ Provider ๋ž˜ํผ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์„ค์ •ํ•˜๋Š” ํŒจํ„ด์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค
  • 'use client' ๊ฐ€ ์™œ ํ•„์š”ํ•œ์ง€, ์–ด๋””์— ๋ถ™์—ฌ์•ผ ํ•˜๋Š”์ง€ ์ดํ•ดํ•œ๋‹ค
// ๐Ÿฆ ์˜ํ˜ธ: "layout.tsx ๋Š” ๊ธฐ๋ณธ์ด Server Component ์•ผ. Provider ๋Š” Client Component ์ด๋ผ์„œ
//          ๋ฐ”๋กœ ์“ธ ์ˆ˜ ์—†์–ด. ๋ž˜ํผ ์ปดํฌ๋„ŒํŠธ๋กœ ๋ถ„๋ฆฌํ•ด์š”."
 
// app/providers.tsx โ€” Client Component ๋ž˜ํผ
'use client'
 
import { Provider } from 'jotai'
import type { ReactNode } from 'react'
 
interface JotaiProviderProps {
  children: ReactNode
}
 
export function JotaiProvider({ children }: JotaiProviderProps) {
  return <Provider>{children}</Provider>
}
// app/layout.tsx โ€” Server Component ์œ ์ง€ (๋ฉ”ํƒ€๋ฐ์ดํ„ฐ, ํฐํŠธ ๋“ฑ ํ™œ์šฉ ๊ฐ€๋Šฅ)
import { JotaiProvider } from '@/app/providers'
import type { Metadata } from 'next'
 
export const metadata: Metadata = {
  title: '์˜์ˆ˜๋„ค ์Šคํ„ฐ๋”” ์ปค๋ฎค๋‹ˆํ‹ฐ',
  description: '๊ฐœ๋ฐœ์ž ์Šคํ„ฐ๋”” ๋งค์นญ ํ”Œ๋žซํผ',
}
 
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ko">
      <body>
        {/*
          JotaiProvider ๋Š” Client Component.
          children (Server Components) ์€ ์—ฌ์ „ํžˆ ์„œ๋ฒ„์—์„œ ๋ Œ๋”๋จ.
          ์ด ๊ตฌ์กฐ๋Š” Next.js์˜ "Client Component ์— Server Component ๋ฅผ ์ž์‹์œผ๋กœ ๋„˜๊ธฐ๊ธฐ" ํŒจํ„ด.
        */}
        <JotaiProvider>
          {children}
        </JotaiProvider>
      </body>
    </html>
  )
}

createStore ๋ฅผ ์ง์ ‘ ๋„˜๊ธฐ๋Š” ๊ณ ๊ธ‰ ํŒจํ„ด

// ๐Ÿฆ ์˜ํ˜ธ: "์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฏธ๋ฆฌ store ์— ์‹ฌ์–ด๋‘๊ณ  ์‹ถ์„ ๋•Œ๋Š” ์ด๋ ‡๊ฒŒ ํ•ด์š”"
// app/providers.tsx
'use client'
 
import { Provider, createStore } from 'jotai'
import { useRef } from 'react'
 
export function JotaiProvider({ children }: { children: React.ReactNode }) {
  // useRef ๋กœ store ๋ฅผ ํ•œ ๋ฒˆ๋งŒ ์ƒ์„ฑ (๋ Œ๋”๋งˆ๋‹ค ์žฌ์ƒ์„ฑ ๋ฐฉ์ง€)
  // ๐Ÿฃ ์˜์ฒ : "useState ๊ฐ€ ์•„๋‹ˆ๋ผ useRef ๋ฅผ ์“ฐ๋Š” ์ด์œ ๊ฐ€ ๋ญ”๊ฐ€์š”?"
  // ๐Ÿฆ ์˜ํ˜ธ: "useRef ๋Š” ๋ฆฌ๋ Œ๋”๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•˜์ง€ ์•Š์•„์„œ store ์ดˆ๊ธฐํ™”์— ์ ํ•ฉํ•ด์š”"
  const storeRef = useRef<ReturnType<typeof createStore>>()
  if (!storeRef.current) {
    storeRef.current = createStore()
  }
 
  return <Provider store={storeRef.current}>{children}</Provider>
}

๐Ÿ’‰ useHydrateAtoms โ€” ์„œ๋ฒ„ ๋ฐ์ดํ„ฐ๋ฅผ atom ์— ์ฃผ์ž…ํ•˜๊ธฐ ๐Ÿ”ด

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • useHydrateAtoms ์˜ ๋™์ž‘ ์›๋ฆฌ์™€ ์‚ฌ์šฉ๋ฒ•์„ ์ •ํ™•ํžˆ ์ดํ•ดํ•œ๋‹ค
  • Server Component ์—์„œ fetch ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ atom ์— ์ดˆ๊ธฐ๊ฐ’์œผ๋กœ ์ฃผ์ž…ํ•˜๋Š” ํŒจํ„ด์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค
// ๊ณต์‹ ๋ ˆํผ๋Ÿฐ์Šค์˜ ํ•ต์‹ฌ ๊ฒฝ๊ณ :
// "useHydrateAtoms ๋Š” ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ์—์„œ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. 'use client' ์ง€์‹œ์–ด๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค."
 
import { useHydrateAtoms } from 'jotai/utils'
 
const countAtom = atom(0)
 
const ClientComponent = ({ countFromServer }: { countFromServer: number }) => {
  // countAtom ์„ countFromServer ๊ฐ’์œผ๋กœ ์ดˆ๊ธฐํ™”
  // ์ด ํ˜ธ์ถœ์€ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ฒ˜์Œ ๋งˆ์šดํŠธ๋  ๋•Œ๋งŒ ํšจ๊ณผ์ 
  useHydrateAtoms([[countAtom, countFromServer]])
 
  const [count] = useAtom(countAtom)
  // count ๋Š” 0 ์ด ์•„๋‹ˆ๋ผ countFromServer ๊ฐ’
  return <div>{count}</div>
}

์ฃผ์˜: atom ์€ store ๋‹น ํ•œ ๋ฒˆ๋งŒ hydrate ๋œ๋‹ค

// โš ๏ธ ์ค‘์š”: ๊ฐ™์€ store ์—์„œ atom ์€ ํ•œ ๋ฒˆ๋งŒ hydrate ๋จ
// initialValue ๊ฐ€ ๋ฆฌ๋ Œ๋” ์‹œ ๋ฐ”๋€Œ์–ด๋„ atom ๊ฐ’์€ ์—…๋ฐ์ดํŠธ๋˜์ง€ ์•Š์•„
 
const Component = ({ count }: { count: number }) => {
  // count prop ์ด 5 โ†’ 10 ์œผ๋กœ ๋ฐ”๋€Œ์–ด๋„ atom ์€ ์—ฌ์ „ํžˆ 5
  useHydrateAtoms([[countAtom, count]])
  // ์ด ๋™์ž‘์€ ์˜๋„๋œ ๊ฒƒ โ€” "์ดˆ๊ธฐ๊ฐ’ ์ฃผ์ž…" ์ด ๋ชฉ์ ์ด๋‹ˆ๊นŒ
}
 
// ๊ฐ•์ œ๋กœ ์žฌ์ฃผ์ž…์ด ํ•„์š”ํ•˜๋ฉด (์œ„ํ—˜ ์ฃผ์˜)
useHydrateAtoms([[countAtom, count]], { dangerouslyForceHydrate: true })

๐Ÿ”— Server โ†’ Client Component ๋ฐ์ดํ„ฐ ์ „๋‹ฌ ์ „์ฒด ํ๋ฆ„ ๐Ÿ”ด

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • Server Component ์—์„œ fetch ํ•˜๊ณ  Client Component ์—์„œ atom ์œผ๋กœ ๊ด€๋ฆฌํ•˜๋Š” ์™„์ „ํ•œ ํŒจํ„ด์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค
// ์ „์ฒด ๋ฐ์ดํ„ฐ ํ๋ฆ„:
// [Server Component] fetch ๋ฐ์ดํ„ฐ
//   โ†“ props ๋กœ ์ „๋‹ฌ
// [Client Component] useHydrateAtoms ๋กœ atom ์— ์ฃผ์ž…
//   โ†“ ์ดํ›„
// [Client ์ƒํƒœ] atom ์œผ๋กœ ์ž์œ ๋กญ๊ฒŒ ์ฝ๊ณ  ์“ฐ๊ธฐ
 
// โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
 
// 1๋‹จ๊ณ„: atom ์ •์˜ (๊ณต์œ  ํŒŒ์ผ)
// atoms/study.ts
import { atom } from 'jotai'
 
export interface Study {
  id: string
  title: string
  likeCount: number
  memberCount: number
}
 
export const studyListAtom = atom<Study[]>([])
export const studyFiltersAtom = atom({ category: 'all', sortBy: 'latest' })
// 2๋‹จ๊ณ„: Server Component โ€” ๋ฐ์ดํ„ฐ fetch
// app/studies/page.tsx (Server Component โ€” 'use client' ์—†์Œ)
import { StudyListClient } from '@/components/StudyListClient'
 
async function fetchStudyList(): Promise<Study[]> {
  // ๐Ÿฆ ์˜ํ˜ธ: "Server Component ์—์„œ fetch ํ•˜๋ฉด ์„œ๋ฒ„์—์„œ ์‹คํ–‰๋ผ์š”.
  //          ์บ์‹ฑ, ํ† ํฐ, DB ์ง์ ‘ ์ ‘๊ทผ ๋ชจ๋‘ ๊ฐ€๋Šฅํ•ด์š”."
  const res = await fetch('https://api.example.com/studies', {
    cache: 'no-store', // ํ•ญ์ƒ ์ตœ์‹  ๋ฐ์ดํ„ฐ
  })
  return res.json()
}
 
export default async function StudiesPage() {
  // ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
  const initialStudyList = await fetchStudyList()
 
  return (
    <main>
      <h1>์Šคํ„ฐ๋”” ๋ชฉ๋ก</h1>
      {/*
        Server Component ์—์„œ fetch ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ Client Component ์— props ๋กœ ์ „๋‹ฌ.
        Client Component ์—์„œ useHydrateAtoms ๋กœ atom ์— ์ฃผ์ž….
      */}
      <StudyListClient initialStudyList={initialStudyList} />
    </main>
  )
}
// 3๋‹จ๊ณ„: Client Component โ€” atom ์— ์ดˆ๊ธฐ๊ฐ’ ์ฃผ์ž… ํ›„ ์‚ฌ์šฉ
// components/StudyListClient.tsx
'use client'
 
import { useAtom, useAtomValue } from 'jotai'
import { useHydrateAtoms } from 'jotai/utils'
import { studyListAtom, studyFiltersAtom } from '@/atoms/study'
 
interface Props {
  initialStudyList: Study[]
}
 
export function StudyListClient({ initialStudyList }: Props) {
  // ๐Ÿฆ ์˜ํ˜ธ: "useHydrateAtoms ๋Š” ๋งˆ์šดํŠธ ์‹œ ํ•œ ๋ฒˆ๋งŒ atom ์— ์ดˆ๊ธฐ๊ฐ’์„ ์‹ฌ์–ด์š”.
  //          ์ดํ›„ atom ์€ ์™„์ „ํžˆ ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ๊ฐ€ ๋ผ์š”."
  useHydrateAtoms([[studyListAtom, initialStudyList]])
 
  // ์ดํ›„ atom ์„ ์ž์œ ๋กญ๊ฒŒ ์‚ฌ์šฉ
  const studies = useAtomValue(studyListAtom)
  const [filters, setFilters] = useAtom(studyFiltersAtom)
 
  const filteredStudies = studies.filter((s) =>
    filters.category === 'all' ? true : s.category === filters.category
  )
 
  return (
    <div>
      <FilterBar filters={filters} onFilterChange={setFilters} />
      <ul>
        {filteredStudies.map((study) => (
          <StudyCard key={study.id} study={study} />
        ))}
      </ul>
    </div>
  )
}
// 4๋‹จ๊ณ„: ์ข‹์•„์š” ๊ฐ™์€ ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ ๋ณ€๊ฒฝ
// components/StudyCard.tsx
'use client'
 
import { useAtom } from 'jotai'
import { studyListAtom } from '@/atoms/study'
 
export function StudyCard({ study }: { study: Study }) {
  const [, setStudyList] = useAtom(studyListAtom)
 
  const handleLike = async () => {
    // ๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ โ€” ์„œ๋ฒ„ ์‘๋‹ต ์ „์— UI ๋จผ์ € ์—…๋ฐ์ดํŠธ
    setStudyList((prev) =>
      prev.map((s) =>
        s.id === study.id ? { ...s, likeCount: s.likeCount + 1 } : s
      )
    )
    // ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์„œ๋ฒ„์— ๋™๊ธฐํ™”
    await fetch(`/api/studies/${study.id}/like`, { method: 'POST' })
  }
 
  return (
    <div>
      <h3>{study.title}</h3>
      <button onClick={handleLike}>โค๏ธ {study.likeCount}</button>
    </div>
  )
}

์—ฌ๋Ÿฌ atom ์„ ํ•œ ๋ฒˆ์— hydrate

// ๐Ÿฃ ์˜์ฒ : "์—ฌ๋Ÿฌ atom ์„ ๋™์‹œ์— ์ดˆ๊ธฐํ™”ํ•  ์ˆ˜ ์žˆ๋‚˜์š”?"
// ๐Ÿฆ ์˜ํ˜ธ: "useHydrateAtoms ๋Š” ๋ฐฐ์—ด์ด๋ผ์„œ ์—ฌ๋Ÿฌ ์Œ์„ ํ•œ ๋ฒˆ์— ๋„˜๊ธธ ์ˆ˜ ์žˆ์–ด์š”"
 
useHydrateAtoms([
  [studyListAtom, initialStudyList],
  [userAtom, initialUser],
  [notificationsAtom, initialNotifications],
])

โš™๏ธ SWC ํ”Œ๋Ÿฌ๊ทธ์ธ ์„ค์ • ๐ŸŸก

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • Jotai SWC ํ”Œ๋Ÿฌ๊ทธ์ธ์ด DX ๋ฅผ ์–ด๋–ป๊ฒŒ ๊ฐœ์„ ํ•˜๋Š”์ง€ ์ดํ•ดํ•œ๋‹ค
  • Next.js ์—์„œ SWC ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์„ค์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•ˆ๋‹ค

Jotai SWC ํ”Œ๋Ÿฌ๊ทธ์ธ์€ ๋‘ ๊ฐ€์ง€ ๊ธฐ๋Šฅ์„ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•ด์ค˜:

  1. debugLabel ์ž๋™ ์ถ”๊ฐ€ โ€” ๊ฐ atom ์— ํŒŒ์ผ๋ช…/๋ณ€์ˆ˜๋ช… ๊ธฐ๋ฐ˜ ๋””๋ฒ„๊ทธ ๋ ˆ์ด๋ธ” ์ž๋™ ์ถ”๊ฐ€
  2. Hot Reload ์ง€์› โ€” ๊ฐœ๋ฐœ ์ค‘ atom ๋ณ€๊ฒฝ ์‹œ ๋น ๋ฅธ HMR
npm install @swc-jotai/debug-label @swc-jotai/react-refresh
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    swcPlugins: [
      // atom ์— ์ž๋™์œผ๋กœ debugLabel ์ถ”๊ฐ€ (DevTools ์—์„œ ์‹๋ณ„ ์šฉ์ด)
      ['@swc-jotai/debug-label', {}],
      // ๊ฐœ๋ฐœ ์ค‘ atom ๋ณ€๊ฒฝ ์‹œ Hot Module Replacement ์ง€์›
      ['@swc-jotai/react-refresh', {}],
    ],
  },
  // jotai-devtools UI CSS ํŠธ๋žœ์ŠคํŒŒ์ผ
  transpilePackages: ['jotai-devtools'],
}
 
module.exports = nextConfig
// SWC ํ”Œ๋Ÿฌ๊ทธ์ธ ์ ์šฉ ์ „
const studyListAtom = atom<Study[]>([])
// debugLabel ์—†์Œ โ†’ DevTools ์—์„œ "atom1", "atom2" ๋กœ ํ‘œ์‹œ
 
// SWC ํ”Œ๋Ÿฌ๊ทธ์ธ ์ ์šฉ ํ›„ (์ž๋™ ๋ณ€ํ™˜)
const studyListAtom = atom<Study[]>([])
studyListAtom.debugLabel = 'studyListAtom' // ์ž๋™ ์ถ”๊ฐ€!
// โ†’ DevTools ์—์„œ 'studyListAtom' ์œผ๋กœ ์ •ํ™•ํ•˜๊ฒŒ ํ‘œ์‹œ

๐Ÿงช SSR ์—์„œ atom ์„ ์ง์ ‘ ์“ฐ์ง€ ์•Š๋Š” ์ด์œ  ๐ŸŸก

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • Server Component ์—์„œ React ํ›…์„ ์“ธ ์ˆ˜ ์—†๋Š” ์ด์œ ๋ฅผ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค
  • ๋ฐ์ดํ„ฐ ํ๋ฆ„์ด "์„œ๋ฒ„ fetch โ†’ props โ†’ Client atom" ์ด์–ด์•ผ ํ•˜๋Š” ์ด์œ ๋ฅผ ์ดํ•ดํ•œ๋‹ค
// โŒ Server Component ์—์„œ atom ์„ ์ง์ ‘ ์“ฐ๋ ค ํ–ˆ์„ ๋•Œ ์—๋Ÿฌ
// app/studies/page.tsx
 
// ์ด๋ ‡๊ฒŒ ํ•˜๊ณ  ์‹ถ์–ด๋„ ์•ˆ ๋จ!
import { useAtomValue } from 'jotai' // โŒ Server Component ์—์„œ ํ›… ์‚ฌ์šฉ ๋ถˆ๊ฐ€
 
export default async function StudiesPage() {
  // Error: Invalid hook call.
  // Hooks can only be called inside of the body of a function component.
  const studies = useAtomValue(studyListAtom) // ๐Ÿ’ฅ ์—๋Ÿฌ!
}
// ๐Ÿฆ ์˜ํ˜ธ: "Server Component ๋Š” React ํ›…์„ ์‹คํ–‰ํ•  ํ™˜๊ฒฝ์ด ์—†์–ด์š”.
//          ํ›…์€ React ๊ฐ€ ์ปดํฌ๋„ŒํŠธ ํŠธ๋ฆฌ๋ฅผ ๊ด€๋ฆฌํ•˜๋ฉด์„œ ์‹คํ–‰๋˜๋Š”๋ฐ,
//          Server Component ๋Š” ์„œ๋ฒ„์—์„œ ํ•œ ๋ฒˆ ์‹คํ–‰๋˜๊ณ  ๋์ด์—์š”."
 
// โœ… ์˜ฌ๋ฐ”๋ฅธ ํŒจํ„ด โ€” Server ์—์„œ fetch, Client ์—์„œ atom ์œผ๋กœ ๊ด€๋ฆฌ
// Server Component: ๋ฐ์ดํ„ฐ๋งŒ ๊ฐ€์ ธ์˜จ๋‹ค
export default async function StudiesPage() {
  const studies = await fetchStudies() // fetch (ํ›… ์•„๋‹˜) โœ…
 
  return <StudyListClient initialStudyList={studies} />
  //      โ†‘ Client Component ์— props ๋กœ ์ „๋‹ฌ
}
 
// Client Component: atom ์œผ๋กœ ์ƒํƒœ ๊ด€๋ฆฌ
'use client'
export function StudyListClient({ initialStudyList }: Props) {
  useHydrateAtoms([[studyListAtom, initialStudyList]]) // ์ฃผ์ž… โœ…
  const studies = useAtomValue(studyListAtom) // ์ดํ›„ atom ์œผ๋กœ ์ž์œ ๋กญ๊ฒŒ ์‚ฌ์šฉ โœ…
}

๐Ÿ’ฅ ์—๋Ÿฌ ํ•ด๊ฒฐ ์นดํƒˆ๋กœ๊ทธ

์—๋Ÿฌ ๋ฉ”์‹œ์ง€๊ฐ€ ๋œจ๋ฉด Ctrl+F ๋กœ ๊ฒ€์ƒ‰ํ•ด๋ด. ๋Œ€๋ถ€๋ถ„ ์—ฌ๊ธฐ ์žˆ์–ด.


โŒ ๋‹ค๋ฅธ ์œ ์ €์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณด์ด๋Š” ํ˜„์ƒ

์›์ธ: layout.tsx ์— <Provider> ์—†์ด ์ „์—ญ atom ์‚ฌ์šฉ (08๋ฒˆ ๊ฐ€์ด๋“œ ์ฐธ๊ณ )

ํ•ด๊ฒฐ์ฑ…:

// app/providers.tsx
'use client'
export function JotaiProvider({ children }: { children: React.ReactNode }) {
  return <Provider>{children}</Provider>
}
 
// app/layout.tsx
import { JotaiProvider } from '@/app/providers'
export default function RootLayout({ children }) {
  return <html><body><JotaiProvider>{children}</JotaiProvider></body></html>
}

โŒ "Invalid hook call" โ€” Server Component ์—์„œ useAtom ์‚ฌ์šฉ

ํ•ด๊ฒฐ์ฑ…: Server Component ์—์„œ ํ›… ์‚ฌ์šฉ ๋ถˆ๊ฐ€. ๋ฐ์ดํ„ฐ๋ฅผ props ๋กœ Client Component ์— ์ „๋‹ฌํ•˜๊ณ  ๊ฑฐ๊ธฐ์„œ useHydrateAtoms ์‚ฌ์šฉ


โŒ useHydrateAtoms ๊ฐ€ prop ์—…๋ฐ์ดํŠธ์— ๋ฐ˜์‘ํ•˜์ง€ ์•Š์Œ

์›์ธ: useHydrateAtoms ๋Š” ๊ฐ™์€ store ์—์„œ ํ•œ ๋ฒˆ๋งŒ ๋™์ž‘ํ•ด

ํ•ด๊ฒฐ์ฑ…:

// ์žฌ์ฃผ์ž…์ด ๊ผญ ํ•„์š”ํ•˜๋ฉด dangerouslyForceHydrate ์˜ต์…˜ ์‚ฌ์šฉ
// (concurrent rendering ์—์„œ ์ž˜๋ชป๋œ ๋™์ž‘ ๊ฐ€๋Šฅ โ€” ์ฃผ์˜ํ•ด์„œ ์‚ฌ์šฉ)
useHydrateAtoms([[countAtom, count]], { dangerouslyForceHydrate: true })

โŒ SWC ํ”Œ๋Ÿฌ๊ทธ์ธ ์ ์šฉ ํ›„ "Module not found" ์—๋Ÿฌ

ํ•ด๊ฒฐ์ฑ…:

# ํ”Œ๋Ÿฌ๊ทธ์ธ ํŒจํ‚ค์ง€ ์„ค์น˜ ํ™•์ธ
npm install @swc-jotai/debug-label @swc-jotai/react-refresh
 
# next.config.js ์˜ experimental.swcPlugins ์„ค์ • ํ™•์ธ

๐Ÿ ์ด๋ฒˆ์— ๋ฐฐ์šด ๋‚ด์šฉ ์ด์ •๋ฆฌ

๐Ÿ“‹ Next.js App Router + Jotai ์ „์ฒด ๊ตฌ์กฐ

app/
โ”œโ”€โ”€ layout.tsx              โ† Server Component + JotaiProvider ๋ž˜ํผ ์‚ฌ์šฉ
โ”œโ”€โ”€ providers.tsx           โ† 'use client' JotaiProvider ๋ž˜ํผ
โ”œโ”€โ”€ studies/
โ”‚   โ””โ”€โ”€ page.tsx            โ† Server Component: fetch ๋ฐ์ดํ„ฐ
โ””โ”€โ”€ components/
    โ””โ”€โ”€ StudyListClient.tsx โ† 'use client': useHydrateAtoms + useAtom

atoms/
โ””โ”€โ”€ study.ts                โ† atom ์ •์˜ (Server/Client ๊ณต์œ )

โš ๏ธ ํ•ต์‹ฌ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

์ฒดํฌ๋‚ด์šฉ
โœ…layout.tsx ์— JotaiProvider ๋ž˜ํผ ์ถ”๊ฐ€
โœ…providers.tsx ์— 'use client' ์ถ”๊ฐ€
โœ…Server Component ์—์„œ๋Š” fetch ๋งŒ, ํ›…์€ Client Component ์—์„œ
โœ…useHydrateAtoms ๋Š” Client Component ('use client')์—์„œ๋งŒ
โœ…SWC ํ”Œ๋Ÿฌ๊ทธ์ธ์œผ๋กœ debugLabel ์ž๋™ํ™”

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

Q1. ๐Ÿšจ ๊ธด๊ธ‰ ๋””๋ฒ„๊น… (์˜์ˆ˜์˜ ํ˜ธํ†ต)

๋ฐฐํฌ ์งํ›„ "A ์œ ์ €๊ฐ€ B ์œ ์ € ์ •๋ณด๋ฅผ ๋ณด๊ณ  ์žˆ๋‹ค" ๊ณ  ์‹ ๊ณ ๊ฐ€ ๋“ค์–ด์™”์–ด.
์˜์ฒ ์ด๊ฐ€ ํ™•์ธํ•˜๋‹ˆ layout.tsx ์— <Provider> ๊ฐ€ ์—†์—ˆ์–ด.
์˜ํ˜ธ ๋‹˜์˜ ๊ธฐ์ˆ ์  ์„ค๋ช…์œผ๋กœ ๊ฐ€์žฅ ์ •ํ™•ํ•œ ๊ฒƒ์€?

  • A) ์„œ๋ฒ„์—์„œ atom ์˜ ์ดˆ๊ธฐ๊ฐ’์ด null ์ด๋ผ์„œ ๋‹ค๋ฅธ ์œ ์ €์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์—ˆ๋‹ค
  • B) Next.js SSR ์—์„œ Provider ์—†์ด atom ์„ ์“ฐ๋ฉด, Node.js ๋ชจ๋“ˆ ๋ ˆ๋ฒจ์˜ ์ „์—ญ store ๊ฐ€ ๋ชจ๋“  ์š”์ฒญ์— ๊ณต์œ ๋˜์–ด ์ด์ „ ์š”์ฒญ์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ๋‹ค์Œ ์š”์ฒญ์— ๋…ธ์ถœ๋œ๋‹ค
  • C) useHydrateAtoms ๋ฅผ ์“ฐ์ง€ ์•Š์•„์„œ ๋ฐœ์ƒํ•œ ๋ฌธ์ œ๋‹ค
  • D) atom ์„ Server Component ์—์„œ ์‚ฌ์šฉํ•ด์„œ ๋ฐœ์ƒํ•œ ๋ฌธ์ œ๋‹ค

โœ… ์ •๋‹ต: B

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

  • ์›๋ฆฌ ์„ค๋ช…: Node.js ์„œ๋ฒ„์—์„œ JavaScript ๋ชจ๋“ˆ์€ ํ•œ ๋ฒˆ ๋กœ๋“œ๋˜์–ด ์บ์‹ฑ๋จ. Provider-less ๋ชจ๋“œ์˜ ์ „์—ญ default store ๋„ ๋ชจ๋“ˆ ๋ ˆ๋ฒจ์—์„œ ํ•œ ๋ฒˆ ์ƒ์„ฑ๋˜์–ด ๊ณต์œ ๋ผ. <Provider> ๋Š” ๋ Œ๋”ํ•  ๋•Œ๋งˆ๋‹ค ๋‚ด๋ถ€์ ์œผ๋กœ createStore() ๋ฅผ ํ˜ธ์ถœํ•ด์„œ ์ƒˆ store ๋ฅผ ๋งŒ๋“ค์–ด. SSR ์—์„œ๋Š” ์š”์ฒญ๋งˆ๋‹ค React ๊ฐ€ ๋ Œ๋”๋ง์„ ์‹คํ–‰ํ•˜๋ฏ€๋กœ, ์š”์ฒญ๋งˆ๋‹ค ์ƒˆ Provider โ†’ ์ƒˆ store โ†’ ์™„์ „ํ•œ ๊ฒฉ๋ฆฌ๊ฐ€ ์ด๋ฃจ์–ด์ ธ.
  • ์˜ค๋‹ต ํ”ผ๋“œ๋ฐฑ: A ๋Š” ์ดˆ๊ธฐ๊ฐ’ ๋ฌธ์ œ๊ฐ€ ์•„๋‹Œ store ๊ณต์œ  ๋ฌธ์ œ์•ผ. C ์™€ D ๋Š” ์‚ฌ๊ณ ์˜ ์›์ธ์ด ์•„๋‹ˆ์•ผ.
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: "SSR + Provider ์—†์Œ = ๊ณต์šฉ ๋ฝ์ปค๋ฃธ. ์ „ ์‚ฌ์šฉ์ž ์˜ท์ด ๊ทธ๋Œ€๋กœ ๋‚จ์•„์žˆ์–ด."

Q2. ๐Ÿ’‰ useHydrateAtoms ์˜ ๋™์ž‘

์˜์ฒ ์ด๊ฐ€ useHydrateAtoms([[countAtom, serverCount]]) ๋ฅผ ์‚ฌ์šฉํ–ˆ์–ด.
์ดํ›„ ์„œ๋ฒ„์—์„œ serverCount prop ์ด 5 ์—์„œ 10 ์œผ๋กœ ๋ฐ”๋€Œ์—ˆ์–ด.
atom ์˜ ๊ฐ’์€ ์–ด๋–ป๊ฒŒ ๋˜๋Š”๊ฐ€?

  • A) ์ž๋™์œผ๋กœ 10 ์œผ๋กœ ์—…๋ฐ์ดํŠธ๋œ๋‹ค
  • B) useHydrateAtoms ๋Š” ์ปดํฌ๋„ŒํŠธ ๋งˆ์šดํŠธ ์‹œ ํ•œ ๋ฒˆ๋งŒ atom ์— ๊ฐ’์„ ์ฃผ์ž…ํ•˜๋ฏ€๋กœ, prop ์ด ๋ฐ”๋€Œ์–ด๋„ atom ์€ ์—ฌ์ „ํžˆ 5 ๋‹ค
  • C) ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค
  • D) atom ์ด ๋ฆฌ์…‹๋˜์–ด ์ดˆ๊ธฐ๊ฐ’ 0 ์œผ๋กœ ๋Œ์•„๊ฐ„๋‹ค

โœ… ์ •๋‹ต: B

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

  • ์›๋ฆฌ ์„ค๋ช…: ๊ณต์‹ ๋ ˆํผ๋Ÿฐ์Šค์— ๋ช…์‹œ๋˜์–ด ์žˆ์–ด: "๊ฐ™์€ store ์—์„œ atom ์€ ํ•œ ๋ฒˆ๋งŒ hydrate ๋œ๋‹ค". useHydrateAtoms ์˜ ๋ชฉ์ ์€ "์ดˆ๊ธฐ๊ฐ’ ์ฃผ์ž…" ์ด์ง€, "prop ๋™๊ธฐํ™”" ๊ฐ€ ์•„๋‹ˆ์•ผ. ๋งˆ์šดํŠธ ํ›„์—๋Š” atom ์ด ๋…๋ฆฝ์ ์ธ ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ๊ฐ€ ๋˜์–ด, ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ๋ณ€๊ฒฝํ•˜๊ฑฐ๋‚˜ setAtom ์„ ํ˜ธ์ถœํ•ด์•ผ ๊ฐ’์ด ๋ฐ”๋€Œ์–ด.
  • ์˜ค๋‹ต ํ”ผ๋“œ๋ฐฑ: A ๋Š” useEffect + setAtom ์œผ๋กœ prop ์„ atom ์— ๋™๊ธฐํ™”ํ•˜๋Š” ๋ณ„๋„ ๋กœ์ง์ด ํ•„์š”ํ•ด.
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: "useHydrateAtoms = ์”จ์•— ์‹ฌ๊ธฐ. ํ•œ ๋ฒˆ ์‹ฌ์œผ๋ฉด ์ดํ›„ ์ž๋ผ๋Š” ๊ฑด ์‹๋ฌผ(atom)์˜ ๋ชซ."

Q3. ๐Ÿ—๏ธ ์•„ํ‚คํ…์ฒ˜ ์„ค๊ณ„

์˜ํ˜ธ ๋‹˜์ด ๋ฌผ์—ˆ์–ด: "Server Component ์—์„œ๋Š” ์™œ atom ์„ ์ง์ ‘ ์“ธ ์ˆ˜ ์—†๋‚˜์š”?"
์˜์ฒ ์ด๊ฐ€ ๋ฉด์ ‘ ์—ฐ์Šต ์‚ผ์•„ ๋Œ€๋‹ตํ•ด๋ดค์–ด. ๊ฐ€์žฅ ์ •ํ™•ํ•œ ๋‹ต๋ณ€์€?

์˜ˆ์‹œ ๋‹ต๋ณ€:

"Server Component ๋Š” ์„œ๋ฒ„์—์„œ ํ•œ ๋ฒˆ ์‹คํ–‰๋˜๊ณ  HTML ์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜์˜ˆ์š”. React ํ›…์€ React ๊ฐ€ ์ปดํฌ๋„ŒํŠธ ํŠธ๋ฆฌ๋ฅผ ๋ฉ”๋ชจ๋ฆฌ์— ์œ ์ง€ํ•˜๋ฉด์„œ ๋ฆฌ๋ Œ๋”์™€ ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ํ•˜๋Š” ์ปจํ…์ŠคํŠธ์—์„œ๋งŒ ๋™์ž‘ํ•ด์š”. Server Component ์—์„œ ํ›…์„ ํ˜ธ์ถœํ•˜๋ฉด 'Invalid hook call' ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•ด์š”. ๊ทธ๋ž˜์„œ Server Component ์—์„œ๋Š” fetch ๋‚˜ DB ์ ‘๊ทผ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ , ๊ทธ ๋ฐ์ดํ„ฐ๋ฅผ Client Component ์— props ๋กœ ์ „๋‹ฌํ•œ ํ›„, Client Component ์—์„œ useHydrateAtoms ๋กœ atom ์— ์ฃผ์ž…ํ•˜๋Š” ํŒจํ„ด์„ ์จ์•ผ ํ•ด์š”."

๐Ÿ’ก ์ด ๋‹ต๋ณ€์„ ์ž์‹ ์˜ ์–ธ์–ด๋กœ ๋งํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด: ์˜ค๋Š˜ ๋ฐฐ์šด ํ•ต์‹ฌ์„ ์™„์ „ํžˆ ์ดํ•ดํ•œ ๊ฑฐ์•ผ.


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

์˜ค๋Š˜์€ ์ง„์งœ ์žŠ์ง€ ๋ชปํ•  ํ•˜๋ฃจ์˜€๋‹ค. ๋ฐฐํฌ 10๋ถ„ ๋งŒ์— ๋ณด์•ˆ ์‚ฌ๊ณ ๊ฐ€ ๋‚˜๋‹ค๋‹ˆ.

์ฒ˜์Œ์—” Provider ๋ผ๋Š” ๊ฒŒ "๊ทธ๋ƒฅ ์žˆ์œผ๋ฉด ์ข‹๊ณ  ์—†์–ด๋„ ๋˜๋Š” ๊ฑฐ" ๋ผ๊ณ  ์ƒ๊ฐํ–ˆ์–ด. ๋กœ์ปฌ์—์„œ ์ž˜ ๋์œผ๋‹ˆ๊นŒ. ๊ทผ๋ฐ Node.js ์„œ๋ฒ„๊ฐ€ ๋ชจ๋“ˆ์„ ์บ์‹ฑํ•œ๋‹ค๋Š” ํŠน์„ฑ ๋•Œ๋ฌธ์—, ์ „์—ญ store ๊ฐ€ ๋ชจ๋“  ์š”์ฒญ์— ๊ณต์œ ๋œ๋‹ค๋Š” ๊ฑธ ์ด๋ฒˆ์— ๋ผˆ์ €๋ฆฌ๊ฒŒ ๋ฐฐ์› ์–ด. ์ด๋ก ์œผ๋กœ ์•Œ๋˜ ๊ฑธ ์‹ค์„œ๋ฒ„ ์‚ฌ๊ณ ๋กœ ๋ฐฐ์šฐ๋Š” ๊ฑด ์™„์ „ํžˆ ๋‹ค๋ฅธ ๊ฒฝํ—˜์ด์•ผ.

์˜ํ˜ธ ๋‹˜์ด ์นจ์ฐฉํ•˜๊ฒŒ 5๋ถ„ ์•ˆ์— ๋กค๋ฐฑํ•˜๊ณ  Provider ์ถ”๊ฐ€ํ•˜๊ณ  ์žฌ๋ฐฐํฌํ–ˆ์„ ๋•Œ ์ง„์งœ ๋Œ€๋‹จํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ์–ด. ๋‚˜๋Š” ์†์ด ๋–จ๋ ค์„œ ํ‚ค๋ณด๋“œ๋ฅผ ๋ชป ์ณค๋Š”๋ฐ. "์‹œ๋‹ˆ์–ด๊ฐ€ ๋˜๋ฉด ์ด๋Ÿฐ ์ƒํ™ฉ์—์„œ๋„ ์นจ์ฐฉํ•ด์ง€๋Š” ๊ฑด๊ฐ€" ์‹ถ์—ˆ์–ด.

๐Ÿ’ก ์˜ค๋Š˜์˜ ๊ตํ›ˆ: "SSR ์—์„œ Provider ๋Š” ์„ ํƒ์ด ์•„๋‹ˆ๋ผ ๋ณด์•ˆ ํ•„์ˆ˜ ์š”์†Œ๋‹ค. ๊ณต์šฉ ๋ƒ‰์žฅ๊ณ ์— ๊ฐœ์ธ ์Œ์‹์„ ๋„ฃ์œผ๋ฉด ๋‹ค๋ฅธ ์‚ฌ๋žŒ์ด ๋จน์–ด๋ฒ„๋ฆฐ๋‹ค."

useHydrateAtoms ํŒจํ„ด๋„ ์˜ค๋Š˜ ์ฒ˜์Œ ์ œ๋Œ€๋กœ ์จ๋ดค์–ด. ์„œ๋ฒ„์—์„œ fetch ํ•˜๊ณ  props ๋กœ ๋„˜๊ธฐ๊ณ  atom ์— ์ฃผ์ž…ํ•˜๋Š” ํ๋ฆ„์ด ์ฒ˜์Œ์—” ๋ณต์žกํ•ด ๋ณด์˜€๋Š”๋ฐ, ํ•ด๋ณด๋‹ˆ๊นŒ ํ™•์‹คํžˆ ๊น”๋”ํ•œ ํŒจํ„ด์ด์•ผ. ์„œ๋ฒ„์˜ ์žฅ์ (๋น ๋ฅธ fetch, ์บ์‹ฑ)๊ณผ ํด๋ผ์ด์–ธํŠธ์˜ ์žฅ์ (์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ์ƒํƒœ ๊ด€๋ฆฌ)๋ฅผ ๊ฐ™์ด ์“ธ ์ˆ˜ ์žˆ์œผ๋‹ˆ๊นŒ.

์˜ค๋Š˜์€ ์ง„์งœ ์ผ์ฐ ์ž์•ผ๊ฒ ๋‹ค. ๊ธด์žฅํ•ด์„œ ์—๋„ˆ์ง€๋ฅผ ๋‹ค ์จ๋ฒ„๋ ธ์–ด. ๋‚ด์ผ ์•„์นจ ์ถœ๊ทผํ•ด์„œ ์˜์ˆ˜ ๋‹˜ํ•œํ…Œ ๊ฐ™์ด ์‚ฌ๊ณผ๋“œ๋ ค์•ผ์ง€.


๐Ÿ”— ๋” ์•Œ์•„๋ณด๊ธฐ