๐Ÿ’ง 07. Next.js SSR๊ณผ ์ƒํƒœ ๊ณต์œ  ๋ˆ„์ˆ˜ ๋ฐฉ์–ด: "์™œ ๋‚ด ํฌ์ธํŠธ๊ฐ€ ๋‚จํ•œํ…Œ ๋ณด์ด์ฃ ?!"

๐Ÿ“‹ ๊ฐœ์š”

Next.js ์„œ๋ฒ„ ํ™˜๊ฒฝ์—์„œ ๊ธ€๋กœ๋ฒŒ ์Šคํ† ์–ด ์‚ฌ์šฉ ์‹œ ๋ฐœ์ƒํ•˜๋Š” ์ƒํƒœ ๋ˆ„์ˆ˜(State Leaking)์˜ ์›๋ฆฌ์™€, Context ๊ธฐ๋ฐ˜์˜ Store per Request ์•„ํ‚คํ…์ฒ˜ ๊ตฌ์ถ•๋ฒ•

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

  • Next.js์˜ SSR ํ™˜๊ฒฝ(Node.js ์„œ๋ฒ„)์—์„œ ๋ชจ๋“ˆ ๋ ˆ๋ฒจ์˜ ์‹ฑ๊ธ€ํ†ค ์ „์—ญ ๋ณ€์ˆ˜๊ฐ€ ์–ด๋–ค ์น˜๋ช…์ ์ธ ๋ณด์•ˆ ์‚ฌ๊ณ ๋ฅผ ๋‚ด๋Š”์ง€ ์›๋ฆฌ๋ฅผ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • Zustand ๊ณต์‹ ๋ฌธ์„œ์—์„œ ๊ถŒ์žฅํ•˜๋Š” Context API + Zustand ํŒฉํ† ๋ฆฌ ํŒจํ„ด์„ ์ ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž(Request)๋งˆ๋‹ค ์™„์ „ํžˆ ๊ฒฉ๋ฆฌ๋œ ์Šคํ† ์–ด๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์ด์ค‘ ๋ Œ๋”๋ง ํ™˜๊ฒฝ์—์„œ์˜ Hydration Mismatch๋ฅผ ๋ฐฉ์–ดํ•˜๋Š” ์ดˆ๊ธฐ ์ƒํƒœ ์ฃผ์ž…๋ฒ•์„ ์ตํž ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ“‹ ๋ชฉ์ฐจ


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

โฑ๏ธ ์˜ˆ์ƒ ์ฝ๊ธฐ ์‹œ๊ฐ„: 18๋ถ„ / ํ•ต์‹ฌ ํŒŒํŠธ: 12๋ถ„

๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ํ๋ฆ„
ํด๋ผ์ด์–ธํŠธ ์‹ฑ๊ธ€ํ†ค vs ์„œ๋ฒ„ ์‹ฑ๊ธ€ํ†ค์˜ ์ฐจ์ด โ†’ ์ƒํƒœ ๋ˆ„์ˆ˜(State Leaking) ์žฌํ˜„ โ†’ Context API๋ฅผ ์ด์šฉํ•œ Request-Per-Store ํŒฉํ† ๋ฆฌ ํŒจํ„ด ๊ฒฐํ•ฉ

๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ๋ฐฐ๊ฒฝ ์„ธ๊ณ„๊ด€: '์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ'

  • ๐Ÿฃ ์˜์ฒ  ( ์‹ ์ž… ): "๋ฆฌ๋“œ ๋‹˜!! ํฐ์ผ ๋‚ฌ์–ด์š”! ์ปค๋ฎค๋‹ˆํ‹ฐ ์•ฑ ์ƒ๋‹จ ํ—ค๋”์— '๋‚ด ํฌ์ธํŠธ ์ž”์•ก'์„ ๋„์šฐ๋ ค๊ณ  Zustand๋กœ useUserStore๋ฅผ ๋งŒ๋“ค๊ณ  Next.js์— ๋ฐฐํฌํ–ˆ๊ฑฐ๋“ ์š”? ๊ทผ๋ฐ ๋ฐฉ๊ธˆ ์˜์ˆ˜ ๋‹˜์ด ์ ‘์†ํ•˜์…จ๋Š”๋ฐ, ๋ณธ์ธ ํฌ์ธํŠธ๊ฐ€ ์•„๋‹ˆ๋ผ ์ œ ํฌ์ธํŠธ ์ž”์•ก์ด ํ™”๋ฉด์— ๋œฌ๋Œ€์š”! ํ•ดํ‚น๋‹นํ•œ ๊ฑด๊ฐ€์š”?!"
  • ๐Ÿฆ ์˜ํ˜ธ ( ๋ฆฌ๋“œ ): "์•„๋‹™๋‹ˆ๋‹ค. ์˜์ฒ  ๋‹˜, ํ•ดํ‚น์ด ์•„๋‹ˆ๋ผ ๋„ฅ์ŠคํŠธ ์„œ๋ฒ„์˜ ๋ฉ”๋ชจ๋ฆฌ ์ŠคํŽ˜์ด์Šค๋ฅผ ๋‚˜๋ˆ  ์ผ๊ธฐ ๋•Œ๋ฌธ์ด์—์š”. ๋ธŒ๋ผ์šฐ์ €์—์„œ Zustand ์Šคํ† ์–ด๋Š” ๋‚˜ ํ˜ผ์ž ์“ฐ๋Š” 1์ธ์šฉ ๋ฝ์ปค์ง€๋งŒ, SSR์„ ๋‹ด๋‹นํ•˜๋Š” Node.js ์„œ๋ฒ„์—์„œ ์„ ์–ธ๋œ ์Šคํ† ์–ด๋Š” ๋ชจ๋“  ์ ‘์†์ž๊ฐ€ ๊ฐ™์ด ์“ฐ๋Š” ๊ณต์ค‘๋ชฉ์š•ํƒ•์ž…๋‹ˆ๋‹ค. ๋‹น์žฅ ๋ฐฐํฌ ๋กค๋ฐฑํ•˜์‹œ๊ณ , ์Šคํ† ์–ด ๊ฒฉ๋ฆฌ ์ˆ˜์ˆ  7์›์น™ ๋“ค์–ด๊ฐ‘๋‹ˆ๋‹ค."

๐Ÿค” ์™œ ์•Œ์•„์•ผ ํ•˜๋Š”๊ฐ€

React ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์—์„œ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌ(useState)ํ•˜์ง€ ์•Š๊ณ  ๋ฐ–์œผ๋กœ ๋นผ๋Š” ๋„๊ตฌ๋ฅผ ๋ชจ๋“ˆ ์ƒํƒœ(Module State) ๋ผ๊ณ  ๋ถ€๋ฆ…๋‹ˆ๋‹ค. Zustand์˜ create ํ•จ์ˆ˜๊ฐ€ ํŒŒ์ผ ์ตœ์ƒ๋‹จ์—์„œ ํ•œ ๋ฒˆ๋งŒ ํ˜ธ์ถœ๋˜๋Š” ๊ฒƒ์ด ๊ทธ ์ฆ๊ฑฐ์ฃ .

ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง(CSR)๋งŒ ํ•˜๋Š” ์•ฑ(Vite, Create React App ๋“ฑ)์—์„œ๋Š” ์ด ๋ฐฉ์‹์ด ์™„๋ฒฝํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์ดํŠธ์— ์ ‘์†ํ•˜๋ฉด ๊ทธ ์‚ฌ๋žŒ์˜ ๋ธŒ๋ผ์šฐ์ € ์ „์šฉ์œผ๋กœ ์ƒˆ๋กœ์šด ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์—”์ง„ ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ํ• ๋‹น๋˜๊ณ , ๊ฑฐ๊ธฐ์„œ ์‹ฑ๊ธ€ํ†ค์œผ๋กœ ์Šคํ† ์–ด๋ฅผ ๋งŒ๋“œ๋‹ˆ๊นŒ์š”. "1 ์œ ์ € = 1 ๋ธŒ๋ผ์šฐ์ € = 1 ์Šคํ† ์–ด" ๊ณต์‹์ด ์„ฑ๋ฆฝํ•ฉ๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ Next.js์˜ SSR (Server-Side Rendering) ํ™˜๊ฒฝ์œผ๋กœ ๋„˜์–ด์˜ค๋ฉด ์ด์•ผ๊ธฐ๊ฐ€ ์™„์ „ํžˆ ๋’ค์ง‘ํž™๋‹ˆ๋‹ค.

์„œ๋ฒ„(Node.js)๋Š” ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค. ์ด ํ•˜๋‚˜์˜ ์„œ๋ฒ„ ํ”„๋กœ๊ทธ๋žจ์ด A ์œ ์ €์˜ ์š”์ฒญ๋„ ์ฒ˜๋ฆฌํ•˜๊ณ , B ์œ ์ €์˜ ์š”์ฒญ๋„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ ํŒŒ์ผ ์ตœ์ƒ๋‹จ์—์„œ create๋œ ์Šคํ† ์–ด๋Š” ์„œ๋ฒ„ ๋Ÿฐํƒ€์ž„์ด ์˜ฌ๋ผ๊ฐ„ ์งํ›„ ๋”ฑ ํ•œ ๋ฒˆ๋งŒ ์ƒ์„ฑ๋œ ์ „์—ญ ์‹ฑ๊ธ€ํ†ค ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค.
A ์œ ์ €๊ฐ€ 1000ํฌ์ธํŠธ๋ฅผ ์Šคํ† ์–ด์— ๋„ฃ๋Š” ์ˆœ๊ฐ„, ์ด 1000ํฌ์ธํŠธ๋Š” ์„œ๋ฒ„ ๋ฉ”๋ชจ๋ฆฌ์— ๊ธฐ๋ก๋˜๊ณ , 0.1์ดˆ ๋’ค ์ ‘์†ํ•œ B ์œ ์ €์˜ HTML์„ ๋ Œ๋”๋งํ•  ๋•Œ ์ด ์Šคํ† ์–ด์˜ 1000ํฌ์ธํŠธ๋ฅผ ๊ทธ๋Œ€๋กœ ์ฝ์–ด์„œ ํ™”๋ฉด์— ๊ทธ๋ ค๋ฒ„๋ฆฌ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค! ์ด๋ฅผ ์ƒํƒœ ์œ ์ถœ(State Leaking / Cross-Request State Pollution) ์ด๋ผ ๋ถ€๋ฆ…๋‹ˆ๋‹ค. ๋Œ€ํ˜• ๋ณด์•ˆ/ํ”„๋ผ์ด๋ฒ„์‹œ ์‚ฌ๊ณ ์˜ ์ฃผ๋ฒ”์ž…๋‹ˆ๋‹ค.


๐Ÿ—๏ธ 1. ์Šคํ† ์–ด ํŒฉํ† ๋ฆฌ ํŒจํ„ด: "๋งค ์š”์ฒญ๋งˆ๋‹ค ์Šคํ† ์–ด๋ฅผ ์ƒˆ๋กœ ๋ฝ‘์ž"

์ด ์žฌ์•™์„ ๋ง‰๊ธฐ ์œ„ํ•ด 5๋…„ ์ฐจ ๊ฐœ๋ฐœ์ž๋“ค์€ ์ƒํƒœ๋ฅผ ์ •์˜ํ•˜๋Š” "ํ‹€(Factory)"๋งŒ ๋งŒ๋“ค์–ด๋‘๊ณ , ์š”์ฒญ(Request)์ด ๋“ค์–ด์˜ฌ ๋•Œ๋งˆ๋‹ค ์ƒˆ๋กœ์šด ๋นˆ ์Šคํ† ์–ด ๊ฐ์ฒด๋ฅผ ์ฐ์–ด๋‚ด๋Š”(Instantiate) ์•„ํ‚คํ…์ฒ˜๋กœ ์„ ํšŒํ•ฉ๋‹ˆ๋‹ค.

๊ธฐ์กด์— create๋ฅผ ๋ฐ”๋กœ ๋•Œ๋ ธ๋˜ ์ฝ”๋“œ๋ฅผ ํŒฉํ† ๋ฆฌ ํ•จ์ˆ˜๋กœ ๊ฐ์Œ‰๋‹ˆ๋‹ค.

// store/user-store.ts
import { createStore } from 'zustand/vanilla'; // ๐Ÿšจ ์ฃผ์˜: ๊ธฐ๋ณธ create๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. ์ฝ”์–ด ์—”์ง„๋งŒ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค!
 
export interface UserState {
  points: number;
  setPoints: (p: number) => void;
}
 
// โŒ ๊ธฐ์กด ์œ„ํ—˜ํ•œ ๋ฐฉ์‹ (์ „์—ญ 1ํšŒ ์ƒ์„ฑ)
// export const useUserStore = create<UserState>(...)
 
// โœ… Pro Approach: ๋งค๋ฒˆ ์ƒˆ ์Šคํ† ์–ด ์ฝ”์–ด(StoreApi)๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ณต์žฅ ํ•จ์ˆ˜
export const createUserStore = (initialPoints: number = 0) => {
  return createStore<UserState>()((set) => ({
    points: initialPoints,
    setPoints: (points) => set({ points }),
  }));
};

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด createUserStore()๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ๋งˆ๋‹ค ์„ธ์ƒ์— ๋‹จ ํ•˜๋‚˜๋ฟ์ธ ์ƒˆ๋กœ์šด ์Šคํ† ์–ด ์ธ์Šคํ„ด์Šค๊ฐ€ ํŠ€์–ด๋‚˜์˜ต๋‹ˆ๋‹ค.


๐Ÿ’Ž 2. Context API์˜ ํ™”๋ คํ•œ ๊ท€ํ™˜

"์ž ๊น, ๊ทธ๋Ÿผ ์ด ์ƒ์„ฑ๋œ ์Šคํ† ์–ด ์ฝ”์–ด(store instance)๋ฅผ ์–ธ์ œ, ์–ด๋””์„œ ๋งŒ๋“ค์–ด์„œ ์ปดํฌ๋„ŒํŠธ์— ๋„˜๊ฒจ์ฃผ๋‚˜์š”? ํ”„๋กญ์Šค๋กœ ๋‹ค ๋šซ๊ณ  ๋‚ด๋ ค๊ฐˆ ๊ฑด๊ฐ€์š”?"

์—ฌ๊ธฐ์„œ ์šฐ๋ฆฌ๊ฐ€ 1ํŽธ์—์„œ ๋ฐ€์–ด๋ƒˆ๋˜ Context API๊ฐ€ ์ตœํ›„์˜ ๊ตฌ์›ํˆฌ์ˆ˜๋กœ ์žฌ๋“ฑ์žฅํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์˜ˆ์ „์ฒ˜๋Ÿผ "์ƒํƒœ ๊ฐ’" ๋ณ€๊ฒฝ์œผ๋กœ ์ธํ•œ ๋ฆฌ๋ Œ๋”๋ง ํญํƒ„ ์šฉ๋„๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. ์˜ค์ง "์ด ํด๋ผ์ด์–ธํŠธ(ํ˜น์€ ์ด SSR ๋ Œ๋” ํŒจ์Šค) ์ „์šฉ์œผ๋กœ ์ƒ์„ฑ๋œ ์Šคํ† ์–ด ์ธ์Šคํ„ด์Šค์˜ ์ฃผ์†Œ๊ฐ’ ํ•˜๋‚˜" ๋งŒ์„ ๋ฐฐ๋‹ฌํ•˜๋Š” ์šฐ์ฒด๋ถ€ ์—ญํ• ๋กœ ์“ฐ์ž…๋‹ˆ๋‹ค. ์ฃผ์†Œ๊ฐ’์€ ์ ˆ๋Œ€ ๋ณ€ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ Context ๋ฆฌ๋ Œ๋”๋ง ํญ๋ฐœ ํ˜„์ƒ๋„ ์ œ๋กœ์ž…๋‹ˆ๋‹ค!

// store/user-store-provider.tsx
'use client'; // Context ๊ด€๋ จ ํ›…์„ ์จ์•ผ ํ•˜๋ฏ€๋กœ Client Component ๊ฒฝ๊ณ„๋ฅผ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค.
 
import { createContext, useContext, useRef } from 'react';
import { useStore } from 'zustand';
import { createUserStore, UserState } from './user-store';
 
// 1. ๋ฐ˜ํ™˜ ํƒ€์ž… ์ถ”์ถœ (StoreApi)
export type UserStoreApi = ReturnType<typeof createUserStore>;
 
// 2. ์šฐ์ฒดํ†ต(Context) ๋งŒ๋“ค๊ธฐ
export const UserStoreContext = createContext<UserStoreApi | undefined>(undefined);
 
// 3. Provider ์ปดํฌ๋„ŒํŠธ: React ์ƒ๋ช…์ฃผ๊ธฐ ๋‚ด์—์„œ ์Šคํ† ์–ด๋ฅผ "1๋ฒˆ๋งŒ" ์ฐ์–ด๋ƒ„
export const UserStoreProvider = ({ children, initialPoints = 0 }) => {
  // ๐Ÿฆ ์˜ํ˜ธ: ์„œ๋ฒ„ ๋ Œ๋”๋ง ์ค‘์ด๋“  ํด๋ผ์ด์–ธํŠธ๋“ , ์ตœ์ดˆ ๋งˆ์šดํŠธ ์‹œ์ ์—๋งŒ ์Šคํ† ์–ด๋ฅผ ์ƒ์„ฑํ•ด์š”.
  const storeRef = useRef<UserStoreApi>();
  
  if (!storeRef.current) {
    storeRef.current = createUserStore(initialPoints);
  }
 
  return (
    <UserStoreContext.Provider value={storeRef.current}>
      {children}
    </UserStoreContext.Provider>
  );
};
 
// 4. ์ปดํฌ๋„ŒํŠธ ์ „์šฉ ์ปค์Šคํ…€ ํ›… ๋งŒ๋“ค๊ธฐ
// ๐Ÿฆ ์˜ํ˜ธ: ์ด์ œ ์•„๋ฌด ๊ณณ์—์„œ๋‚˜ 1ํŽธ 2ํŽธ์ฒ˜๋Ÿผ Selector๋ฅผ ๋˜์ง€๋ฉด, 
// ์ „์—ญ ๋ณ€์ˆ˜๊ฐ€ ์•„๋‹Œ ์ด ์ปจํ…์ŠคํŠธ ์•ˆ์˜ '๋‚ด ์ „์šฉ ์Šคํ† ์–ด'์—์„œ ๊ฐ’์„ ๊บผ๋‚ด์˜ต๋‹ˆ๋‹ค.
export const useUserStore = <T,>(selector: (store: UserState) => T): T => {
  const context = useContext(UserStoreContext);
  if (!context) throw new Error('Provider๋กœ ๊ฐ์‹ธ์ฃผ์„ธ์š”!');
  
  // zustand ํŒจํ‚ค์ง€์˜ ๊ธฐ๋ณธ useStore๋ฅผ ์ด์šฉํ•ด ์ฝ”์–ด๋ฅผ ๋ฆฌ์•กํŠธ์— ๊ฒฐํ•ฉํ•ฉ๋‹ˆ๋‹ค.
  return useStore(context, selector);
};

๐Ÿงฉ 3. Next.js App Router ์— ๋ฐ”๋ฅด๋Š” ๋ฒ• (Hydration ๋™๊ธฐํ™”)

์ด์ œ ํŒฉํ† ๋ฆฌ์™€ ํ”„๋กœ๋ฐ”์ด๋”์˜ ์„ธํŒ…์ด ๋๋‚ฌ์Šต๋‹ˆ๋‹ค. ์œ ์ € ์ •๋ณด ์กฐํšŒ API๋ฅผ ์˜๋Š” ์ตœ์ƒ๋‹จ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ(๋ ˆ์ด์•„์›ƒ ๋“ฑ)์—์„œ ๊ป๋ฐ๊ธฐ๋ฅผ ๊ฐ์‹ธ์ฃผ๊ณ  ์„œ๋ฒ„ ๋ฐ์ดํ„ฐ ์ดˆ๊นƒ๊ฐ’์„ ๋ฐ€์–ด ๋„ฃ์–ด์ค๋‹ˆ๋‹ค.

// app/layout.tsx (Server Component)
import { UserStoreProvider } from '@/store/user-store-provider';
import { getUserPointsFromDB } from '@/lib/db';
 
export default async function RootLayout({ children }) {
  // ๐Ÿ‘” ์˜์ˆ˜: SSR ํ™˜๊ฒฝ์—์„œ DB๋ฅผ ์ฐ”๋Ÿฌ A์œ ์ €์˜ ์ฐ ํฌ์ธํŠธ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
  const initPoints = await getUserPointsFromDB();
 
  return (
    <html lang="en">
      <body>
        {/* ๐Ÿฆ ์˜ํ˜ธ: ์ด ์ˆœ๊ฐ„ ์ƒ์„ฑ๋œ Provider ๋‚ด์˜ ์Šคํ† ์–ด๋Š” ์˜ค์ง ์ด A์œ ์ €์˜ ์š”์ฒญ ๋ Œ๋”๋ง ํŒจ์Šค์—์„œ๋งŒ ์œ ํšจํ•ฉ๋‹ˆ๋‹ค! */}
        <UserStoreProvider initialPoints={initPoints}>
          {children}
        </UserStoreProvider>
      </body>
    </html>
  );
}

์ด์ œ ํ•˜์œ„์˜ ๋ชจ๋“  ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ(์˜์ฒ ์ด ๋งŒ๋“  ๊ธ€์“ฐ๊ธฐ ํผ, ํ—ค๋”, ์žฅ๋ฐ”๊ตฌ๋‹ˆ ๋“ฑ)์—์„œ๋Š” ์ต์ˆ™ํ•˜๊ฒŒ const points = useUserStore(state => state.points); ๋ฅผ ํ˜ธ์ถœํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

  • ์„œ๋ฒ„ ๋ Œ๋”๋ง ๋‹จ๊ณ„: initPoints (์˜ˆ: 1000)๊ฐ€ ๋ฐ”๋กœ ๋ Œ๋”๋ง๋˜์–ด HTML์— ๋ฐ•ํž˜.
  • ํด๋ผ์ด์–ธํŠธ Hydration ๋‹จ๊ณ„: useRef๊ฐ€ HTML ๊ฒฐ๊ณผ๋ฌผ๊ณผ ์™„๋ฒฝํžˆ ๋™๊ธฐํ™”๋œ 1000์„ ๋ฐ”๋ผ๋ณด๋ฉฐ ์ดˆ๊ธฐํ™”๋˜๋ฏ€๋กœ Mismatch 0%!
  • A์œ ์ €์™€ B์œ ์ €๋Š” ๊ฐ๊ฐ์˜ ์š”์ฒญ๋งˆ๋‹ค ์ƒ์„ฑ๋œ <Provider>์˜ ์„œ๋กœ ๋‹ค๋ฅธ ref ๊ฐ์ฒด๋ฅผ ๋ณด๊ฒŒ ๋˜๋ฏ€๋กœ ์ƒํƒœ ๋ˆ„์ˆ˜ ์™„๋ฒฝ ์ฐจ๋‹จ!

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

Q1. Next.js App Router (SSR ํ™˜๊ฒฝ)์—์„œ ๋ชจ๋“ˆ ์Šค์ฝ”ํ”„(ํŒŒ์ผ ์ตœ์ƒ๋‹จ)์— Zustand ์Šคํ† ์–ด ์ธ์Šคํ„ด์Šค๋ฅผ ๋‹จ์ผ๋กœ ์„ ์–ธํ•˜๋ฉด ์ ˆ๋Œ€ ์•ˆ ๋˜๋Š” ์น˜๋ช…์ ์ธ ์ด์œ ๋Š” ๋ฌด์—‡์ธ๊ฐ€์š”?

  • A) ๋ธŒ๋ผ์šฐ์ € ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ํ„ฐ์ ธ ๋‚˜๊ฐ„๋‹ค.
  • B) Node.js ์„œ๋ฒ„ ํ”„๋กœ์„ธ์Šค ํ•˜๋‚˜๋ฅผ ์—ฌ๋Ÿฌ ๋ Œ๋”๋ง ์š”์ฒญ์ด ๊ณต์œ ํ•˜๋ฏ€๋กœ, A ์œ ์ €์˜ ์ƒํƒœ๊ฐ€ B ์œ ์ €์˜ ๋ Œ๋”๋ง ๊ฒฐ๊ณผ(HTML)์— ์„ž์—ฌ ๋‚˜๊ฐ€๋Š” ์ƒํƒœ ์œ ์ถœ(State Leaking)์ด ๋ฐœ์ƒํ•œ๋‹ค.
  • C) ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์—์„œ๋Š” TypeScript ์—๋Ÿฌ๊ฐ€ ๊ฐ•์ œ ๋ฐœ์ƒํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
  • D) Zustand ๊ฐœ๋ฐœํŒ€์ด ๋ฒ•์ ์œผ๋กœ ๊ธˆ์ง€ํ–ˆ๋‹ค.

โœ… ์ •๋‹ต: B

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

  • ์›๋ฆฌ ์„ค๋ช…: ์„œ๋ฒ„๋Š” ํ•œ ๋†ˆ์ด์ง€๋งŒ, ์š”์ฒญํ•˜๋Š” ์œ ์ €๋Š” ์ˆ˜๋งŒ ๋ช…์ž…๋‹ˆ๋‹ค. ํŒŒ์ผ ์ตœ์ƒ๋‹จ์—์„œ ์ „์—ญ ๋ณ€์ˆ˜๋กœ ํ• ๋‹น๋œ 1๊ฐœ์˜ ์Šคํ† ์–ด ํ•˜๋‚˜์— API๋กœ ๊ฐ€์ ธ์˜จ A ์œ ์ €์˜ ๋ฏผ๊ฐ ์ •๋ณด๋ฅผ ๋•Œ๋ ค ๋ฐ•์œผ๋ฉด, ๊ทธ ์งํ›„ B ์œ ์ € ํ™”๋ฉด์„ ๋ Œ๋”๋งํ•˜๋˜ Node ๋Ÿฐํƒ€์ž„์ด ๊ทธ ์˜ค์—ผ๋œ ์Šคํ† ์–ด๋ฅผ ์ฝ์–ด์„œ HTML์„ ๋ฐ˜ํ™˜ํ•ด๋ฒ„๋ฆฝ๋‹ˆ๋‹ค.
  • ์˜ค๋‹ต ํ”ผ๋“œ๋ฐฑ: "์˜์ฒ  ๋‹˜, CSR์—์„  ๊ดœ์ฐฎ์•˜์ง€๋งŒ ๋ฐฑ์—”๋“œ๊ฐ€ ๊ฐœ์ž…ํ•˜๋Š” ์ˆœ๊ฐ„ ๋ชจ๋‘์˜ ๊ณต์ค‘๋ชฉ์š•ํƒ•์ด๋ผ๋Š” ๊ฐœ๋…์„ ๋ช…์‹ฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค."
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: ๐Ÿฆ ์˜ํ˜ธ: "ํด๋ผ์ด์–ธํŠธ ์Šคํ† ์–ด๋Š” ๊ฐœ์ธ ๋ผ์ปค๋ฃธ, ์„œ๋ฒ„ ์Šคํ† ์–ด(์ „์—ญ)๋Š” ๊ณต์šฉ ๋ชฉ์š•ํƒ•์ž…๋‹ˆ๋‹ค."

Q2. SSR์—์„œ ์‚ฌ์šฉ์ž๋ณ„๋กœ ๊ฒฉ๋ฆฌ๋œ(Request-Per-Store) ์•ˆ์ „ํ•œ Zustand ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ๊ตฌ์ถ•ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๋Š” ๋””์ž์ธ ํŒจํ„ด์€ ๋ฌด์—‡์ธ๊ฐ€์š”?

  • A) Immer ๋ฏธ๋“ค์›จ์–ด + React.memo
  • B) LocalStorage๋ฅผ ์„œ๋ฒ„ ์ฟ ํ‚ค๋กœ ๋ฎ์–ด์”Œ์›Œ์„œ ๋ณต์‚ฌํ•˜๊ธฐ
  • C) ๋ชจ๋“ˆ ๋ณ€์ˆ˜๋ฅผ ํ•จ์ˆ˜ ์•ˆ์œผ๋กœ ์ด๋™์‹œํ‚ค๋Š” ์‹ฑ๊ธ€ํ†ค ํŠธ๋ฆฌ ํŒจํ„ด
  • D) ์Šคํ† ์–ด ์ธ์Šคํ„ด์Šค ํŒฉํ† ๋ฆฌ(Factory) + Context API ํ”„๋กœ๋ฐ”์ด๋” ๊ฒฐํ•ฉ

โœ… ์ •๋‹ต: D

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

  • ์›๋ฆฌ ์„ค๋ช…: ์ƒํƒœ๋ฅผ ์ •์˜ํ•˜๋Š” ํŒฉํ† ๋ฆฌ ํ•จ์ˆ˜(createStore())๋งŒ ๋งŒ๋“ค์–ด๋‘๊ณ , ์š”์ฒญ์ด ๋“ค์–ด์™€ ์ตœ์ƒ์œ„ ๋ Œ๋”๋ง์ด ์‹œ์ž‘๋  ๋•Œ(useRef) ์ƒˆ๋กœ์šด ๋นˆ ์Šคํ† ์–ด ๊ฐ์ฒด๋ฅผ ์ฐ์–ด๋ƒ…๋‹ˆ๋‹ค. ์ด ๊ฐ“ ํƒœ์–ด๋‚œ ์Šคํ† ์–ด ์ฃผ์†Œ๊ฐ’์„ React์˜ Context API์— ๋‹ด์•„ ์ž์‹๋“ค์—๊ฒŒ ๋ฟŒ๋ฆฌ๋ฉด ์„œ๋ฒ„ ๋ฉ”๋ชจ๋ฆฌ ํ’€์ด ์™„๋ฒฝํ•˜๊ฒŒ ๊ฒฉ๋ฆฌ๋ฉ๋‹ˆ๋‹ค.
  • ์˜ค๋‹ต ํ”ผ๋“œ๋ฐฑ: "์˜์ฒ  ๋‹˜, Context๋Š” ๋” ์ด์ƒ ์ƒํƒœ ๋ฆฌ๋ Œ๋”๋ง ํญํƒ„์ด ์•„๋‹ˆ์—์š”. ๊ทธ์ € ์Šคํ† ์–ด ์ฃผ์†Œ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ์ „๋‹ฌํ•˜๋Š” ์•„์ฃผ ๋‹จ๋‹จํ•œ ํŒŒ์ดํ”„๊ด€์ผ ๋ฟ์ž…๋‹ˆ๋‹ค."
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: ๐Ÿฆ ์˜ํ˜ธ: "Context๊ฐ€ ์šฐ์ฒด๋ถ€ ์—ญํ• ์„ ํ•ด์„œ, ์œ ์ €๋งˆ๋‹ค ๊ณ ์œ ํ•œ ์Šคํ† ์–ด์˜ ์ฃผ์†Œ์ง€๋ฅผ ์ •ํ™•ํžˆ ๊ฝ‚์•„์ค๋‹ˆ๋‹ค."

Q3. ๐Ÿ‹๏ธโ€โ™‚๏ธ ์˜์ฒ ์˜ ํ…Œ์ŠคํŠธ ํƒ€์ž„ (Q&A)

์˜์ฒ ์ด๊ฐ€ ์‡ผํ•‘ ์•ฑ์„ Next.js(Pages Router/App Router)๋กœ ๊ฐœ๋ฐœ ์ค‘์ž…๋‹ˆ๋‹ค. '๋‹คํฌ ๋ชจ๋“œ' ์—ฌ๋ถ€๋ฅผ ์ €์žฅํ•˜๋Š” UI ํ…Œ๋งˆ ์Šคํ† ์–ด(useThemeStore)๋Š” SSR ์ƒํƒœ ๋ˆ„์ˆ˜ ์œ„ํ—˜์„ ๋ง‰๊ธฐ ์œ„ํ•ด ๋ฌด์กฐ๊ฑด ์œ„์™€ ๊ฐ™์€ Provider ํŒฉํ† ๋ฆฌ ํŒจํ„ด์œผ๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํ•ด์•ผ ํ• ๊นŒ์š”?

โœ… ์ •๋‹ต: ์•„๋‹™๋‹ˆ๋‹ค. ์„œ๋ฒ„ ์ƒํ˜ธ์ž‘์šฉ์ด ์ „ํ˜€ ์—†๋Š” ์ˆœ์ˆ˜ํ•œ ํด๋ผ์ด์–ธํŠธ ๋กœ์ง์ด๋ผ๋ฉด ๊ธฐ์กด create ์ „์—ญ ํŒจํ„ด์„ ์œ ์ง€ํ•ด๋„ ๋ฉ๋‹ˆ๋‹ค.

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

  • ์›๋ฆฌ ์„ค๋ช…: ์ƒํƒœ ๋ˆ„์ˆ˜๋Š” "SSR" ์‹œ์ ์— "์‚ฌ์šฉ์ž๋ณ„ ๊ณ ์œ ํ•œ ๋ฐ์ดํ„ฐ"๊ฐ€ ์ „์—ญ ๊ณต๊ฐ„์— ์„ž์ผ ๋•Œ ํญ๋ฐœํ•˜๋Š” ์ด์Šˆ์ž…๋‹ˆ๋‹ค. ๋‹คํฌ ๋ชจ๋“œ, ์ž„์‹œ ํผ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ํ…์ŠคํŠธ ๋“ฑ์€ ์–ด์ฐจํ”ผ ์‚ฌ์šฉ์ž ๋ธŒ๋ผ์šฐ์ € ๋ Œ๋”๋ง ํŒจ์Šค(useEffect ํ˜น์€ Client Component)์—์„œ๋งŒ ๊ตด๋Ÿฌ๊ฐ€๋Š” ๊ฐ’๋“ค์ด๋ฉฐ, ์„œ๋ฒ„ ๋ Œ๋”๋ง ์‹œ์ ์— ์˜์กด์„ฑ์„ ๊ฐœ์ž…์‹œํ‚ฌ ์ด์œ ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. (Next.js ์บ์‹œ ํ™˜๊ฒฝ ๊ทœ์น™์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ์ง€๋งŒ, ๊ทผ๋ณธ ์ •๋ณด์˜ ์†Œ์œ ์ž ๊ด€์ ์—์„  ๋ฌดํ•ดํ•ฉ๋‹ˆ๋‹ค.)
  • ์˜ค๋‹ต ํ”ผ๋“œ๋ฐฑ: "์˜์ฒ  ๋‹˜, ์ „์—ญ์œผ๋กœ ๋งŒ๋“ค์—ˆ๋‹ค๊ณ  ๋‹ค ์—๋Ÿฌ๊ฐ€ ๋‚˜๋Š” ๊ฒŒ ์•„๋‹ˆ์—์š”. ์Šคํ† ์–ด์— ๋‹ด๊ธด ์ƒํƒœ๊ฐ€ **ํŠน์ • ์œ ์ €์˜ ๋ฏผ๊ฐ ์ •๋ณด(ํ† ํฐ, ํฌ์ธํŠธ ๋“ฑ SSR ๊ณผ์ •์—์„œ ์ฃผ์ž…๋˜์–ด์•ผ ํ•˜๋Š” ๊ฐ’)**์ธ๊ฐ€? ๋ผ๋Š” ๋ณธ์งˆ์„ ๋”ฐ์ง€์„ธ์š”. ํŒ์—… ์—ด๋ฆผ/๋‹ซํž˜ ์ฒ˜๋ฆฌ์— ์ € ๊ฑฐ์ฐฝํ•œ ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ๋ฅผ ํƒœ์šฐ๋Š” ๊ฑด ํˆฌ๋จธ์น˜์ž…๋‹ˆ๋‹ค."
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: ๐Ÿฆ ์˜ํ˜ธ: "๋‚ด ๋…ธํŠธ๋ถ์—์„œ๋งŒ ๋„๋Š” ๊ฑด ์ „์—ญ ์Šคํ† ์–ด. ์„œ๋ฒ„๋ฅผ ๊ฑฐ์น˜๋ฉฐ ๋‚จํ•œํ…Œ ๋ณด์—ฌ์„  ์•ˆ ๋  ๋ฐ์ดํ„ฐ๊ฐ€ ์„ž์ด๋Š” ๊ฑด Provider ์Šคํ† ์–ด."

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

์™€. ์˜ค๋Š˜ ์„œ๋ฒ„ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ์–˜๊ธฐ๋ฅผ ๋“ค์œผ๋ฉด์„œ ๋‚ด ๋จธ๋ฆฌ์นด๋ฝ์ด ๋ฐ˜์ฏค ๋น ์ง€๋Š” ์ค„ ์•Œ์•˜๋‹ค. ์•„๋ฌด ์ƒ๊ฐ ์—†์ด Vercel์— ์˜ฌ๋ฆฌ๊ณ  ๋‹ค ๋๋‹ค๊ณ  ์ข‹์•„ํ–ˆ๋Š”๋ฐ, ์ƒํƒœ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํ•˜๋‚˜์กฐ์ฐจ๋„ ์„œ๋ฒ„์‚ฌ์ด๋“œ(SSR) ๊ฐœ๋…์ด ๋ผ์–ด๋“ค๋ฉด ์•„ํ‚คํ…์ฒ˜๋ฅผ ๋’ค์ง‘์–ด์—Ž์–ด์•ผ ํ•œ๋‹ค๋Š” ์‚ฌ์‹ค์— ์ถฉ๊ฒฉ์„ ๋ฐ›์•˜๋‹ค. ๊ทธ๋ž˜๋„ ๋ฐ”๋‹๋ผ ์ฝ”์–ด ๋ชจ๋ธ(zustand/vanilla)์ด๋ž‘ Context API๋ฅผ ์„ž์–ด ์“ฐ๋Š” ์˜ํ˜ธ ๋ฆฌ๋“œ ๋‹˜์˜ ์šฐ์•„ํ•œ ๋ฐฉ์–ด ์ฝ”๋“œ๋ฅผ ๋ณด๋‹ˆ๊นŒ ๋‚˜๋„ ์ €๋Ÿฐ ์‹œ๋‹ˆ์–ด๊ฐ€ ๋˜๊ณ  ์‹ถ๋‹ค๋Š” ์ „ํˆฌ๋ ฅ์ด ๋ถˆํƒ€์˜ค๋ฅธ๋‹ค! (๊ทธ๋ž˜๋„ ํ‡ด๊ทผ ํ›„์—” ๋จธ๋ฆฌ๊ฐ€ ํ•‘ํ•‘ ๋„๋‹ˆ๊นŒ ์น˜ํ‚จ ์ƒ๊ฐ๋งŒ ๋‚œ๋‹ค... ๋‚ด์ผ ํšŒ์‚ฌ ๊ฐ€๋ฉด ์ด ํŒจํ„ด๋ถ€ํ„ฐ ์™ธ์›Œ์•ผ์ง€.)

๐Ÿ’ก "์ƒํƒœ๋ฅผ ๋ฐ•์ œํ•˜์ง€ ๋ง๊ณ  ๋งค๋ฒˆ ์ƒˆ ์ƒ์ž๋ฅผ ๋‚ด์–ด์ฃผ์ž. ์„œ๋ฒ„ ๋ฉ”๋ชจ๋ฆฌ๋Š” ๋งŒ์ธ์˜ ๊ณต์ค‘ํ™”์žฅ์‹ค์ด๋‹ค."