๋งํฌ ๋๋ผํผ ์ด๊ธฐ ๊ฐ๋ฐ ๋น์,
์ด๋ ๋ ์ฌ์ฉ์ A๊ฐ ์ ๋ณด๋ฅผ ํด์์ต๋๋ค.
โ๋ก๊ทธ์ธํ๋๋ ๋ค๋ฅธ ์ฌ๋ ์ด๋ฉ์ผ์ด ๋ ์โฆโ
์๊ฐ ๋ฑ๊ณจ์ด ์๋ํด์ก์ต๋๋ค.
ํ๋ผ์ด๋ฒ์๊ฐ ์ค์ํ ์๋น์ค์์
๋ค๋ฅธ ์ฌ์ฉ์์ ์ ๋ณด๊ฐ ๋ณด์ด๋ ๊ฑด ์น๋ช
์ ์ธ ๋ฌธ์ ์์ฃ .
ํ์ธํด๋ณด๋,
Zustand๋ก ๋ง๋ ์ฌ์ฉ์ ์ํ(useUserStore
)๊ฐ
์๋ฒ์์ ์ฌ๋ฌ ์ฌ์ฉ์์๊ฒ ๊ณต์ ๋๊ณ ์์์ต๋๋ค.
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ํ๊ฒ ๊ณต์ ๋๊ณ ์์๋ ๊ฑฐ์ฃ .
์ํ๊ด๋ฆฌ | SSR ์ํ ๊ณต์ ๊ฐ๋ฅ์ฑ | ๋์ ํ์ ์ฌ๋ถ |
---|---|---|
Zustand | โ ๋์ | ์ง์ ๋ถ๋ฆฌ ํ์ |
Redux | โ ๋์ | createStore() ๋ก ์์ฒญ๋ง๋ค ๋ถ๋ฆฌ |
Jotai | โ ์์ | atom context ๋ถ๋ฆฌ ํ์ |
Recoil | โ ๋ฎ์ | ๊ธฐ๋ณธ์ ์ผ๋ก ํด๋ผ์ด์ธํธ ์ ์ฉ |
Context | โ ๋ฎ์ | ์ปดํฌ๋ํธ ๊ธฐ๋ฐ ์ํ, SSR ๊ณต์ ์์ |
๋๋ถ๋ถ์ ์ํ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ SSR ํ๊ฒฝ์์ 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 ํ๊ฒฝ์ด๋ ๋ณต์กํ ๊ตฌ์กฐ์์๋ ๋ ๋์ ๋์๋ ์์ต๋๋ค.
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 ์ค๊ณ๋ฅผ ๋๋ฐํด์ผ ํฉ๋๋ค.
์ด ๊ธ์ด ์ ์ฒ๋ผ ์ด ๋ฌธ์ ๋ฅผ ๋ชฐ๋ผ์ ๊ณ ์ํ ๊ฐ๋ฐ์๋ถ๋ค์๊ฒ
์์ ๊ฒฝ๊ณ ์ด์ ๋์ ๋๊ธฐ๋ฅผ ๋ฐ๋๋๋ค.
๊ถ๊ธํ ์ ์ด๋ ๊ฐ์ ์์ด๋์ด๊ฐ ์๋ค๋ฉด ๋๊ธ๋ก ๋จ๊ฒจ์ฃผ์ธ์ ๐