분명 로컬 스토리지에는 값이 있다.
개발자 도구를 열어보면 내가 바꾼 설정이 아주 멀쩡하게 들어 있다.
그런데 새로고침을 누르는 순간, UI는 아무 일도 없었다는 듯 다시 초기 상태로 돌아간다.
“음…? 내가 뭘 놓친 거지?”
처음엔 hydrate 타이밍 문제인가 싶었다. useEffect에서 초기화를 한 건 아닌지, React Strict Mode 때문인지, zustand가 초기 state를 먼저 써버리는 건 아닌지…
별의별 가능성을 다 의심해봤다. 그런데 이상했다.
저장은 되는데, 복원만 안 된다. 😵
대시보드 앱에서 유저 워크스페이스 설정을 관리하는 스토어를 만들었다고 가정하자. 😉
사이드바 접힘 여부, 테마(light/dark) 같은 값들이다.
구조적으로 깔끔해 보이도록, 모든 설정을 settings라는 객체 안에 모아두었다.
type WorkspaceStore = {
state: {
settings: {
sidebarCollapsed: boolean;
theme: 'light' | 'dark';
};
};
actions: {
toggleSidebar: () => void;
setTheme: (theme: 'light' | 'dark') => void;
};
};
persist도 자연스럽게 붙였다.
persist(
(set) => ({
state: {
settings: {
sidebarCollapsed: false,
theme: 'light',
},
},
actions: { ... },
}),
{
name: 'WORKSPACE_STORAGE',
partialize: (state) => ({ state: state.state }),
}
);
이 시점에서는 아무 문제도 없었다. 새로고침해도 상태는 잘 유지됐다.
문제는 여기서 시작됐다. 코드 리뷰에서 이런 피드백을 받았다.

“
state.state구조가 조금 이상해 보여요. state 중복을 없애는 게 좋지 않을까요?”
나도 그 말에 바로 수긍했고 다음처럼 바꿨다.
partialize: (state) => state.state
중복을 제거했고, 코드도 훨씬 깔끔해 보였다.
그리고 바로, 문제가 시작됐다... 😈
이제는 이런 현상이 발생했다.
이때가 제일 헷갈렸다.
“아니… 아까까지 잘 되던 게 state 중복만 없앴을 뿐인데 왜 망가졌지?”
여기서 중요한 포인트는 이거다.
리뷰는 맞았고, 수정도 논리적으로 맞았지만 persist 관점에서는 치명적인 변경이었다.
partialize: (state) => state.state 로 바꾸면서 localStorage에 저장되는 값은 이렇게 변했다.
{
"settings": {
"theme": "dark",
"sidebarCollapsed": false
}
}
하지만 Zustand persist는 이 값을 스토어의 최상위(root)에 얕게 병합한다. 결과적으로 hydrate 이후 내부 상태는 이런 모습이 된다.
{
state: {
settings: {
theme: 'light',
sidebarCollapsed: false,
},
},
actions: { ... },
settings: {
theme: 'dark',
sidebarCollapsed: false,
},
}
그리고 나는 계속 store.state.settings.theme만 보고 있었다.
값은 복원됐지만, 내가 보고 있는 위치가 아니었다. 💡
이게 바로 “저장은 되는데, 복원이 안 되는 것처럼 보였던” 이유였다.
이쯤 와서 깨달았다.
이건 partialize의 문제가 아니라,
persist + state/actions 패턴의 궁합 문제라는 걸.
persist를 쓰는 스토어에서는 구조를 “논리적으로 예쁜 것”보다 “직렬화와 병합에 정직한 형태” 로 가져가는 게 중요했다.
그래서 결국 store를 flat 하게 바꿨다.
type WorkspaceStore = {
sidebarCollapsed: boolean;
theme: 'light' | 'dark';
toggleSidebar: () => void;
setTheme: (theme: 'light' | 'dark') => void;
};
persist(
(set) => ({
sidebarCollapsed: false,
theme: 'light',
toggleSidebar: () =>
set((s) => ({ sidebarCollapsed: !s.sidebarCollapsed })),
setTheme: (theme) => set({ theme }),
}),
{ name: 'WORKSPACE_STORAGE' }
);
이후로는 GOOD 👍
partialize는 구조를 바꾸지 않는다
→ 단지 “어디에 저장할지”만 바꾼다.
persist store는 flat 하게
→ 중첩 구조 + shallow merge = 디버깅 지옥