๐ง 07. Next.js SSR๊ณผ ์ํ ๊ณต์ ๋์ ๋ฐฉ์ด: "์ ๋ด ํฌ์ธํธ๊ฐ ๋จํํ ๋ณด์ด์ฃ ?!"
๐ ๊ฐ์
Next.js ์๋ฒ ํ๊ฒฝ์์ ๊ธ๋ก๋ฒ ์คํ ์ด ์ฌ์ฉ ์ ๋ฐ์ํ๋ ์ํ ๋์(State Leaking)์ ์๋ฆฌ์, Context ๊ธฐ๋ฐ์ Store per Request ์ํคํ ์ฒ ๊ตฌ์ถ๋ฒ
๐ฏ ์ด ์น์ ์ ์ฝ๊ณ ๋๋ฉด:
- Next.js์ SSR ํ๊ฒฝ(Node.js ์๋ฒ)์์ ๋ชจ๋ ๋ ๋ฒจ์ ์ฑ๊ธํค ์ ์ญ ๋ณ์๊ฐ ์ด๋ค ์น๋ช ์ ์ธ ๋ณด์ ์ฌ๊ณ ๋ฅผ ๋ด๋์ง ์๋ฆฌ๋ฅผ ์ค๋ช ํ ์ ์๋ค.
- Zustand ๊ณต์ ๋ฌธ์์์ ๊ถ์ฅํ๋ Context API + Zustand ํฉํ ๋ฆฌ ํจํด์ ์ ์ฉํ์ฌ ์ฌ์ฉ์(Request)๋ง๋ค ์์ ํ ๊ฒฉ๋ฆฌ๋ ์คํ ์ด๋ฅผ ์์ฑํ ์ ์๋ค.
- ์ด์ค ๋ ๋๋ง ํ๊ฒฝ์์์ Hydration Mismatch๋ฅผ ๋ฐฉ์ดํ๋ ์ด๊ธฐ ์ํ ์ฃผ์ ๋ฒ์ ์ตํ ์ ์๋ค.
๐ ๋ชฉ์ฐจ
- ๐ค ์ ์์์ผ ํ๋๊ฐ
- ๐๏ธ 1. ์คํ ์ด ํฉํ ๋ฆฌ ํจํด: "๋งค ์์ฒญ๋ง๋ค ์คํ ์ด๋ฅผ ์๋ก ๋ฝ์"
- ๐ 2. Context API์ ํ๋ คํ ๊ทํ
- ๐งฉ 3. Next.js App Router ์ ๋ฐ๋ฅด๋ ๋ฒ (Hydration ๋๊ธฐํ)
- ๐๏ธโโ๏ธ ์์ฒ ์ ํ ์คํธ ํ์ (Q&A)
๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
โฑ๏ธ ์์ ์ฝ๊ธฐ ์๊ฐ: 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๋ฅผ ์์ด ์ฐ๋ ์ํธ ๋ฆฌ๋ ๋์ ์ฐ์ํ ๋ฐฉ์ด ์ฝ๋๋ฅผ ๋ณด๋๊น ๋๋ ์ ๋ฐ ์๋์ด๊ฐ ๋๊ณ ์ถ๋ค๋ ์ ํฌ๋ ฅ์ด ๋ถํ์ค๋ฅธ๋ค! (๊ทธ๋๋ ํด๊ทผ ํ์ ๋จธ๋ฆฌ๊ฐ ํํ ๋๋๊น ์นํจ ์๊ฐ๋ง ๋๋ค... ๋ด์ผ ํ์ฌ ๊ฐ๋ฉด ์ด ํจํด๋ถํฐ ์ธ์์ผ์ง.)
๐ก "์ํ๋ฅผ ๋ฐ์ ํ์ง ๋ง๊ณ ๋งค๋ฒ ์ ์์๋ฅผ ๋ด์ด์ฃผ์. ์๋ฒ ๋ฉ๋ชจ๋ฆฌ๋ ๋ง์ธ์ ๊ณต์คํ์ฅ์ค์ด๋ค."