๐Ÿ˜ฑ ๋กœ๊ทธ์ธํ–ˆ๋”๋‹ˆ ๋‹ค๋ฅธ ์‚ฌ๋žŒ ์ •๋ณด๊ฐ€?

LinkDropperยท2025๋…„ 4์›” 24์ผ
27
post-thumbnail

"์–ด..? ์ด๊ฑฐ ์ œ ์ •๋ณด ์•„๋‹Œ๋ฐ์š”?"

๋งํฌ ๋“œ๋ผํผ ์ดˆ๊ธฐ ๊ฐœ๋ฐœ ๋‹น์‹œ,
์–ด๋А ๋‚  ์‚ฌ์šฉ์ž A๊ฐ€ ์ œ๋ณด๋ฅผ ํ•ด์™”์Šต๋‹ˆ๋‹ค.

โ€œ๋กœ๊ทธ์ธํ–ˆ๋”๋‹ˆ ๋‹ค๋ฅธ ์‚ฌ๋žŒ ์ด๋ฉ”์ผ์ด ๋– ์š”โ€ฆโ€

์ˆœ๊ฐ„ ๋“ฑ๊ณจ์ด ์„œ๋Š˜ํ•ด์กŒ์Šต๋‹ˆ๋‹ค.
ํ”„๋ผ์ด๋ฒ„์‹œ๊ฐ€ ์ค‘์š”ํ•œ ์„œ๋น„์Šค์—์„œ
๋‹ค๋ฅธ ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๊ฐ€ ๋ณด์ด๋Š” ๊ฑด ์น˜๋ช…์ ์ธ ๋ฌธ์ œ์˜€์ฃ .

ํ™•์ธํ•ด๋ณด๋‹ˆ,
Zustand๋กœ ๋งŒ๋“  ์‚ฌ์šฉ์ž ์ƒํƒœ(useUserStore)๊ฐ€
์„œ๋ฒ„์—์„œ ์—ฌ๋Ÿฌ ์‚ฌ์šฉ์ž์—๊ฒŒ ๊ณต์œ ๋˜๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.


SSR์—์„œ Zustand๋ฅผ ์“ฐ๋ฉด ์ƒ๊ธฐ๋Š” ๋ฌธ์ œ

Next.js๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ SSR(์„œ๋ฒ„์‚ฌ์ด๋“œ ๋ Œ๋”๋ง)์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.
ํŽ˜์ด์ง€ ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋ฉด ์„œ๋ฒ„์—์„œ HTML์„ ์ƒ์„ฑํ•˜๊ณ , ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๋ธŒ๋ผ์šฐ์ €์— ์ „๋‹ฌํ•˜์ฃ .

๊ทธ๋Ÿฐ๋ฐ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธฐ๋Š” ํฌ์ธํŠธ๋Š” ์ด๊ฒ๋‹ˆ๋‹ค.

// store.ts
import { create } from 'zustand';

export const useUserStore = create((set) => ({
  user: null,
  setUser: (user) => set({ user }),
}));

์ด๋ ‡๊ฒŒ ๋งŒ๋“  store๋Š” Node ์„œ๋ฒ„ ๋ฉ”๋ชจ๋ฆฌ์— ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.
์ฆ‰, ์‚ฌ์šฉ์ž A๊ฐ€ ๋กœ๊ทธ์ธํ•ด์„œ ์ƒํƒœ๊ฐ€ ์„ค์ •๋˜๋ฉด
์‚ฌ์šฉ์ž B๊ฐ€ ์ƒˆ ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ A์˜ ์ •๋ณด๊ฐ€ ๋‚จ์•„ ์žˆ์„ ์ˆ˜ ์žˆ๋Š” ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค.

์„œ๋ฒ„๋Š” statelessํ•ด์•ผ ํ•˜๋Š”๋ฐ, store๋Š” statefulํ•˜๊ฒŒ ๊ณต์œ ๋˜๊ณ  ์žˆ์—ˆ๋˜ ๊ฑฐ์ฃ .


Zustand๋งŒ์˜ ๋ฌธ์ œ๋Š” ์•„๋‹™๋‹ˆ๋‹ค

์ƒํƒœ๊ด€๋ฆฌSSR ์ƒํƒœ ๊ณต์œ  ๊ฐ€๋Šฅ์„ฑ๋Œ€์‘ ํ•„์š” ์—ฌ๋ถ€
Zustandโœ… ๋†’์Œ์ง์ ‘ ๋ถ„๋ฆฌ ํ•„์š”
Reduxโœ… ๋†’์ŒcreateStore()๋กœ ์š”์ฒญ๋งˆ๋‹ค ๋ถ„๋ฆฌ
Jotaiโœ… ์žˆ์Œatom context ๋ถ„๋ฆฌ ํ•„์š”
RecoilโŒ ๋‚ฎ์Œ๊ธฐ๋ณธ์ ์œผ๋กœ ํด๋ผ์ด์–ธํŠธ ์ „์šฉ
ContextโŒ ๋‚ฎ์Œ์ปดํฌ๋„ŒํŠธ ๊ธฐ๋ฐ˜ ์ƒํƒœ, SSR ๊ณต์œ  ์—†์Œ

๋Œ€๋ถ€๋ถ„์˜ ์ƒํƒœ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” SSR ํ™˜๊ฒฝ์—์„œ store ๋ถ„๋ฆฌ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.


โœ… ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• 1: ์š”์ฒญ๋งˆ๋‹ค store๋ฅผ ์ƒˆ๋กœ ์ƒ์„ฑํ•˜๊ธฐ

Zustand๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ createStore()๋ฅผ ํ†ตํ•ด ์ƒˆ๋กœ์šด store ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์ด๋ฅผ ํ†ตํ•ด SSR ํ™˜๊ฒฝ์—์„œ๋„ ์‚ฌ์šฉ์ž๋ณ„ ์ƒํƒœ๋ฅผ ๊ฒฉ๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// store.ts
import { createStore } from 'zustand';

let store: any;

export const createUserStore = () => createStore(() => ({
  user: null,
  setUser: (user: any) => set({ user }),
}));

export const getUserStore = () => {
  if (typeof window === 'undefined') {
    return createUserStore(); // SSR: ํ•ญ์ƒ ์ƒˆ๋กœ์šด store ์ƒ์„ฑ
  }
  if (!store) store = createUserStore(); // CSR: ์‹ฑ๊ธ€ํ†ค ์œ ์ง€
  return store;
};

์ด ๋ฐฉ์‹์€ ๊ฐ„๋‹จํ•˜๊ณ  ์œ ์šฉํ•˜์ง€๋งŒ,
Next.js App Router ํ™˜๊ฒฝ์ด๋‚˜ ๋ณต์žกํ•œ ๊ตฌ์กฐ์—์„œ๋Š” ๋” ๋‚˜์€ ๋Œ€์•ˆ๋„ ์žˆ์Šต๋‹ˆ๋‹ค.


โœ… ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• 2: React Context๋กœ store๋ฅผ ์ฃผ์ž…ํ•˜๊ธฐ

createUserStore()๋กœ ์ƒ์„ฑํ•œ store๋ฅผ
React Context๋กœ ์ปดํฌ๋„ŒํŠธ ํŠธ๋ฆฌ์— ์ฃผ์ž…ํ•˜๋Š” ๋ฐฉ์‹๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
Redux์˜ Provider ํŒจํ„ด๊ณผ ๋น„์Šทํ•˜์ง€๋งŒ ํ›จ์”ฌ ๊ฐ€๋ณ๊ณ  ์œ ์—ฐํ•ฉ๋‹ˆ๋‹ค.

// store.ts
import { createStore } from 'zustand';

export const createUserStore = () =>
  createStore(() => ({
    user: null,
    setUser: (user: any) => set({ user }),
  }));
// user-context.tsx
'use client';
import { createContext, useContext, useRef } from 'react';
import { StoreApi, useStore } from 'zustand';
import { createUserStore } from './store';

const UserStoreContext = createContext<StoreApi<any> | null>(null);

export const UserStoreProvider = ({ children }: { children: React.ReactNode }) => {
  const storeRef = useRef(createUserStore());
  return (
    <UserStoreContext.Provider value={storeRef.current}>
      {children}
    </UserStoreContext.Provider>
  );
};

export const useUserStore = <T,>(selector: (state: any) => T) => {
  const store = useContext(UserStoreContext);
  if (!store) throw new Error('useUserStore must be used within <UserStoreProvider>');
  return useStore(store, selector);
};
// layout.tsx or page.tsx
import { UserStoreProvider } from './user-context';

export default function Layout({ children }) {
  return <UserStoreProvider>{children}</UserStoreProvider>;
}

์ด ๋ฐฉ์‹์€ ์š”์ฒญ๋ณ„๋กœ ์™„์ „ํžˆ ์ƒˆ๋กœ์šด ์ƒํƒœ๋ฅผ ์ฃผ์ž…ํ•  ์ˆ˜ ์žˆ์–ด.
๋ฏผ๊ฐํ•œ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ์—๋„ ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค.


์ •๋ฆฌํ•˜์ž๋ฉด

ํ•ด๊ฒฐ ๋ฐฉ์‹ํŠน์ง•์ถ”์ฒœ ์ƒํ™ฉ
getUserStore() ํŒจํ„ด๊ฐ„๋‹จํ•˜๊ณ  ๋น ๋ฆ„CSR ์œ„์ฃผ ์•ฑ, ์†Œ๊ทœ๋ชจ ํ”„๋กœ์ ํŠธ
Context ์ฃผ์ž… ๋ฐฉ์‹SSR ์™„๋ฒฝ ๋Œ€์‘App Router, ๋ฏผ๊ฐํ•œ ์ƒํƒœ ๋‹ค๋ฃฐ ๋•Œ

๐Ÿงช ๋งํฌ ๋“œ๋ผํผ, ์ง€๊ธˆ ๋ฒ ํƒ€ ํ…Œ์ŠคํŠธ ์ค‘์ž…๋‹ˆ๋‹ค

์šฐ๋ฆฌ๊ฐ€ ๊ฒช์€ ์ด ๋ฌธ์ œ๋Š” ๋‹จ์ˆœํ•œ ๊ฐœ๋ฐœ ์ด์Šˆ๊ฐ€ ์•„๋‹ˆ๋ผ,
์„œ๋น„์Šค ์‹ ๋ขฐ์„ฑ๊ณผ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์˜ ๊ทผ๋ณธ์„ ๊ฑด๋“œ๋ฆฌ๋Š” ๋ฌธ์ œ์˜€์Šต๋‹ˆ๋‹ค.
์ด๋Ÿฐ ๊ณ ๋ฏผ ๋์— ํƒ„์ƒํ•œ ์„œ๋น„์Šค๊ฐ€ ๋ฐ”๋กœ ๋งํฌ ๋“œ๋ผํผ์ž…๋‹ˆ๋‹ค.

๋งํฌ๋ฅผ ์ €์žฅํ•˜๋ฉด
๐Ÿ“Œ ์ œ๋ชฉ, ์„ค๋ช…, ์ธ๋„ค์ผ์ด ์ž๋™์œผ๋กœ ๋ถˆ๋Ÿฌ์™€์ง€๊ณ 
๐Ÿ“‚ ํด๋” ๋‹จ์œ„๋กœ ์ •๋ฆฌํ•˜๊ฑฐ๋‚˜ ๊ณต์œ ๋„ ๊ฐ€๋Šฅํ•ด์š”.
์ €ํฌ๋Š” ๋‹จ์ˆœํžˆ ์ €์žฅํ•˜๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ
๋‹ค์‹œ ๊บผ๋‚ด๋ณด๋Š” ๋งํฌ ๊ฒฝํ—˜์„ ๋งŒ๋“ค๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ‘‰ ๐Ÿ”— ๋งํฌ ๋“œ๋ผํผ ๋ฒ ํƒ€ ์ฒดํ—˜ํ•˜๋Ÿฌ ๊ฐ€๊ธฐ

์‚ฌ์šฉํ•ด๋ณด์‹œ๊ณ  ํ”ผ๋“œ๋ฐฑ๋„ ๋‚จ๊ฒจ์ฃผ์‹œ๋ฉด ์ •๋ง ํฐ ํž˜์ด ๋ฉ๋‹ˆ๋‹ค :)


๋งˆ์น˜๋ฉฐ

๋งํฌ ๋“œ๋ผํผ ์ดˆ๊ธฐ์— ๊ฒช์—ˆ๋˜ ์ด์Šˆ๋Š”
๋‹จ์ˆœํ•œ ์ƒํƒœ๊ด€๋ฆฌ ์‹ค์ˆ˜๊ฐ€ ์–ผ๋งˆ๋‚˜ ํฐ ๋ณด์•ˆ ๋ฌธ์ œ๋กœ ์ด์–ด์งˆ ์ˆ˜ ์žˆ๋Š”์ง€๋ฅผ ๋ณด์—ฌ์คฌ์Šต๋‹ˆ๋‹ค.

Zustand๋Š” ์ •๋ง ๊ฐ€๋ณ๊ณ  ์ข‹์€ ๋„๊ตฌ์ง€๋งŒ,
Next.js SSR ํ™˜๊ฒฝ์—์„œ๋Š” ๋ฐ˜๋“œ์‹œ ์•ˆ์ „ํ•œ store ์„ค๊ณ„๋ฅผ ๋™๋ฐ˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ด ๊ธ€์ด ์ €์ฒ˜๋Ÿผ ์ด ๋ฌธ์ œ๋ฅผ ๋ชฐ๋ผ์„œ ๊ณ ์ƒํ•  ๊ฐœ๋ฐœ์ž๋ถ„๋“ค์—๊ฒŒ
์ž‘์€ ๊ฒฝ๊ณ ์ด์ž ๋„์›€ ๋˜๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค.


๊ถ๊ธˆํ•œ ์ ์ด๋‚˜ ๊ฐœ์„  ์•„์ด๋””์–ด๊ฐ€ ์žˆ๋‹ค๋ฉด ๋Œ“๊ธ€๋กœ ๋‚จ๊ฒจ์ฃผ์„ธ์š” ๐Ÿ™Œ

profile
โ€œ๊ธฐ๋กํ•˜๋Š” ์Šต๊ด€์„ ๋„๊ตฌ๋กœ ๋งŒ๋“ค๋‹ค โ€” ๋‘ ๊ฐœ๋ฐœ์ž์˜ ๋งํฌ ๋“œ๋ผํผ ๊ตฌ์ถ•๊ธฐโ€

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