왜 개발자들은 Zustand를 상태 관리 도구로 선택할까

프론트엔드 개발에서 상태 관리는 늘 쉬운 주제가 아닙니다. 처음에는 컴포넌트 내부 state만으로 충분해 보이지만, 기능이 늘고 화면이 복잡해질수록 상태는 점점 여기저기 흩어지기 시작합니다.
로그인 정보, 사용자 설정, 공통 UI 상태처럼 여러 컴포넌트가 함께 써야 하는 값이 늘어나면, 상태 관리는 개발의 생산성을 좌우하는 핵심 과제가 됩니다.
많은 개발자들이 Context API나 Redux를 도입해 보지만, 막상 써보면 보일러플레이트 코드가 과도하게 늘어나거나 설정이 복잡해 ‘이 정도 규모에 꼭 여기까지 필요할까?’라는 고민이 뒤따릅니다.
이런 고민을 해결할 대안으로 주목받는 도구가 있습니다. 바로 ‘Zustand’입니다. 복잡한 설정 없이 상태 관리를 시작할 수 있고, 번들 크기도 가벼우며, TypeScript 지원도 우수해 점진적으로 도입하기에 부담이 적다는 평가를 받고 있습니다.
Zustand는 기존 상태 관리 방식과 무엇이 다르기에 많은 개발자들이 선택하고 있을까요? 이제부터 Zustand가 무엇인지, 어떤 특징을 가진 도구인지 살펴보겠습니다.
Zustand란?

Zustand는 React 애플리케이션에서 전역 상태를 관리하기 위해 만들어진 경량 상태 관리 라이브러리입니다.
전통적인 상태 관리 도구들이 갖고 있던 규칙과 패턴을 단순화해, 상태를 더 직관적으로 다룰 수 있도록 설계 상태 관리에 필요한 최소한의 기능만을 제공하며, 복잡한 설정이나 구조를 요구하지 않는 것이 특징입니다.
더 단순하고 더 충실한 상태 관리 방식을 위한 ‘Zustand’
Zustand는 2019년, 독일 베를린을 기반으로 활동하는 오픈소스 컬렉티브 Poimandres에서 공개되었습니다.
당시 React 생태계에서는 Redux의 복잡성과 Context API의 한계에 대한 피로감이 점차 커지고 있었고, Poimandres는 더 단순하면서도 실무에 충분한 상태 관리 방식을 목표로 Zustand를 설계했습니다.
Zustand는 특정 프레임워크나 패턴을 강요하지 않는 접근 방식을 취합니다. 상태 관리가 하나의 철학이나 규율이 되기보다는, 필요한 만큼만 선택해 사용할 수 있는 도구가 되기를 지향했습니다.
이러한 배경 속에서 Zustand는 비교적 짧은 시간 안에 React 개발자들 사이에서 실무 친화적인 상태 관리 라이브러리로 자리 잡게 되었습니다.
단순해지기 위해 시작했지만,
결국 부담이 되어버린 상태 관리

상태 관리는 애플리케이션이 현재 어떤 상황에 있는지를 값으로 관리하는 것을 말합니다. 화면에 무엇이 보이는지, 사용자가 어떤 상태에 있는지와 같은 정보를 기준으로 애플리케이션의 동작을 결정합니다.
본래는 애플리케이션의 구조를 단순하게 만들고, 유지보수를 수월하게 하기 위해 사용됩니다. 하지만 프론트엔드 애플리케이션이 커지면서 상태 관리는 점점 부담이 되기 시작했습니다.
처음에는 컴포넌트 내부 state만으로 충분했던 기능들이, 로그인 정보나 공통 UI 상태처럼 여러 화면에서 함께 사용하는 값이 늘어나기 시작했습니다.
이러한 과정에서 상태는 자연스럽게 전역으로 확장되었고, 상태 관리는 구현의 문제가 아니라 관리의 문제로 느껴지기 시작했습니다.
구조를 단순하게 만들려다 복잡해진 Context API
Context API는 비교적 간단한 해결책처럼 보였습니다. 별도의 라이브러리 없이 전역 상태를 공유할 수 있었기 때문입니다. 하지만 상태가 많아질수록 Provider가 중첩되고, 컴포넌트 구조가 점점 복잡해졌습니다.
전역 상태를 관리하기 위해 도입했지만, 오히려 컴포넌트 트리를 따라 상태 흐름을 추적해야 하는 부담이 생기기도 했죠. 상태 하나를 추가할 때마다 구조를 다시 확인해야 하는 상황이 반복되면서, 관리 부담은 점점 커졌습니다.
명확한 규칙 대신 무거운 준비가 필요한 Redux
Redux와 같은 전통적인 상태 관리 도구는 명확한 규칙과 일관된 구조를 제공했습니다. 하지만 그만큼 준비해야 할 요소도 많았습니다.
상태 하나를 추가하기 위해 액션과 리듀서, 타입을 함께 고려해야 했고, 프로젝트 규모에 비해 구조가 과하다고 느껴지는 경우도 적지 않았습니다.
상태 관리를 도입했음에도 불구하고, 개발 속도나 코드 가독성 측면에서는 오히려 부담이 커졌다고 느끼는 순간들이 반복되었습니다.
이처럼 상태 관리는 필요했지만, 기존 방식들은 점점 상태 관리를 무겁고 부담스러운 작업으로 만들기 시작했습니다.
Zustand, 필요한 만큼만 과하지 않게

Zustand는 무겁고 부담스러워진 상태 관리를 해결하기 위한 대안으로 주목받은 상태 관리 도구입니다. 복잡한 규칙보다는 실제 사용 경험에 초점을 맞춘 설계를 선택한 것이 특징이죠. 기존 방식과 어떤 점이 다른지 살펴보겠습니다.
러닝 커브가 낮은 상태 관리
Zustand는 상태 관리 도구를 처음 도입하는 상황에서도 비교적 빠르게 이해하고 사용할 수 있는 구조를 가지고 있습니다. 복잡한 개념이나 규칙을 먼저 학습하지 않아도, React에서 state를 다뤄본 경험이 있다면 자연스럽게 접근할 수 있습니다. 상태를 정의하고 사용하는 방식이 직관적이어서, 도입 단계에서의 학습 부담이 크지 않습니다.
- 별도의 개념 정리 없이 바로 사용 가능
- 기존 React 개발 경험과 크게 다르지 않은 사용 방식
- 상태 관리 도입에 따른 초기 진입 장벽이 낮음
불필요한 보일러플레이트가 없는 구조
Zustand는 액션 타입이나 리듀서 구조를 필수로 요구하지 않습니다. 상태와 상태 변경 로직을 한곳에서 간단하게 정의할 수 있어, 코드가 불필요하게 길어지지 않습니다. 이로 인해 상태 관리 코드가 구현 자체보다 구조를 설명하는 데 쓰이는 상황을 줄일 수 있습니다.
- 반복적인 코드 작성 부담 감소
- 상태 정의와 변경 흐름이 비교적 명확
- 유지보수 시 코드 파악이 수월함
React에 자연스럽게 녹아드는 사용 방식
Zustand는 React hook 형태로 상태를 사용하도록 설계되어 있어, 컴포넌트 코드 안에서 자연스럽게 활용할 수 있습니다. 별도의 Provider 설정 없이도 전역 상태를 사용할 수 있으며, selector를 통해 필요한 상태만 선택적으로 구독할 수 있습니다. 이 방식은 불필요한 리렌더링을 줄이는 데에도 도움이 됩니다.
- hook 기반으로 상태 접근
- Provider 없이 전역 상태 사용 가능
- 필요한 상태만 구독해 렌더링 영향 최소화
작은 프로젝트부터 확장 가능한 특성
Zustand는 처음부터 모든 상태를 전역으로 설계할 필요가 없습니다. 일부 공통 상태부터 관리하다가, 프로젝트 규모와 복잡도가 커질수록 점진적으로 확장할 수 있습니다. 이로 인해 작은 서비스나 MVP 단계에서도 부담 없이 도입할 수 있고, 상태 관리가 필요한 지점에 맞춰 유연하게 적용할 수 있습니다.
- 소규모 프로젝트에서도 무리 없이 사용 가능
- 필요에 따라 상태 관리 범위 조절 가능
- 초기 설계 부담 없이 점진적 확장 가능
Zustand vs Redux vs Context vs Recoil
상태 관리도구, 어떤 차이가 있을까

프론트엔드 상태 관리를 고민하다 보면 자연스럽게 이 네 가지 선택지를 마주하게 됩니다. 모두 전역 상태를 다루기 위한 도구이지만, 지향하는 방향과 사용 방식에는 분명한 차이가 있습니다. 어떤 도구가 더 낫다고 단정하기보다는, 어떤 상황에서 어떤 선택이 더 적절한지를 기준으로 살펴볼 필요가 있습니다.
Redux, 일관성을 위해 구조를 선택한 상태 관리
Redux는 명확한 규칙과 일관된 구조를 강점으로 하는 상태 관리 도구입니다. 상태 변경 흐름이 예측 가능하도록 설계되어 있어, 팀 단위 협업이나 대규모 프로젝트에서 안정성을 확보하는 데 유리합니다.
반면 액션, 리듀서, 타입 정의 등 준비해야 할 요소가 많아 초기 설정과 유지에 드는 비용이 높은 편입니다. 상태 관리 자체가 하나의 아키텍처가 되는 성격을 가지고 있습니다.
Context API, 간단한 공유를 위한 기본 도구
Context API는 React에 기본으로 포함된 기능으로, 전역 값을 컴포넌트 트리 전반에 전달하기 위한 수단에 가깝습니다. 간단한 설정 값이나 공통 데이터를 공유하는 용도로는 충분히 유용합니다.
하지만 상태가 많아질수록 Provider가 중첩되고, 상태 흐름이 컴포넌트 구조에 강하게 의존하게 됩니다. 복잡한 상태 변경 로직을 관리하기에는 한계가 있는 방식입니다.
Recoil, 복잡한 상태 관계를 명확하게 풀어내는 방식
Recoil은 atom과 selector를 기반으로 상태를 작은 단위로 나누어 관리하는 방식이 특징입니다. 상태 간 의존 관계를 명확하게 표현할 수 있어, 복잡한 상태 로직을 다루는 데 강점이 있습니다.
다만 상태 구조 설계에 대한 이해가 필요하고, 단순한 전역 상태 관리에는 다소 부담스럽게 느껴질 수 있습니다.
Zustand, 필요한 만큼만 가볍게 사용하는 상태 관리
Zustand는 Redux처럼 무거운 구조를 요구하지 않으면서도, Context API보다 명확하게 전역 상태를 관리할 수 있는 방식을 제공합니다.
상태와 상태 변경 로직을 간단하게 정의하고, 필요한 상태만 선택적으로 사용하는 구조로 설계되어 있어 학습 부담과 코드 복잡도를 낮추는 데 초점을 둡니다. 실무에서 빠르게 적용하기 쉬운 상태 관리 도구로 평가받는 이유입니다.
Zustand의 주요 특징 4가지

다른 상태도구와의 차이에서도 보았듯이 Zustand는 필요한 상태만 선택해서 사용할 수 있어 부담이 적고 빠르게 적용하기 쉬운 상태 관리도구로 평가받고 있는데요. 이러한 평가를 받을 수 있는 비결이 무엇일까요? Zustand의 주요 특징 4가지에 대해 살펴보겠습니다.
1) 간단한 Store 구조
Zustand는 상태를 하나의 store로 정의하는 단순한 구조를 가지고 있습니다. 상태와 상태 변경 로직을 한 곳에서 관리할 수 있어, 상태 관리 코드의 흐름을 한눈에 파악하기 쉽습니다. 복잡한 디렉터리 구조나 여러 단계의 설정 없이도 전역 상태를 구성할 수 있다는 점이 특징입니다.
2) 선택적 상태 구독 방식
Zustand는 store 전체를 구독하는 방식이 아니라, 컴포넌트가 필요한 상태만 선택적으로 구독할 수 있도록 설계되어 있습니다. 이로 인해 전역 상태를 사용하더라도 상태 간 결합도가 낮아지고, 각 컴포넌트가 자신의 역할에 집중할 수 있습니다.
3) 불필요한 리렌더링 최소화
선택적 상태 구독 방식은 자연스럽게 렌더링 성능에도 영향을 줍니다. 상태가 변경되더라도 해당 상태를 실제로 사용하는 컴포넌트만 다시 렌더링되기 때문에, 전역 상태 사용으로 인한 불필요한 리렌더링을 줄일 수 있습니다. 이는 상태 관리 규모가 커질수록 체감되는 장점입니다.
4) TypeScript와 확장성을 함께 고려한 구조
Zustand는 TypeScript와 함께 사용할 때 타입 정의가 비교적 단순하며, 상태 구조와 변경 로직을 명확하게 표현할 수 있습니다.
또한 필요에 따라 미들웨어를 통해 상태 영속화나 개발 도구 연동 등 기능을 확장할 수 있어, 기본은 단순하지만 요구사항에 맞춰 유연하게 확장할 수 있는 구조를 갖추고 있습니다.
Zustand 간단한 사용법

Zustand의 기본적인 사용 흐름은 store를 하나 만들고, 컴포넌트에서 필요한 상태를 불러오는 방식입니다.
1. Zustand 설치하기
먼저 Zustand를 프로젝트에 설치합니다.
| npm install zustand |
2. store 만들기
전역으로 사용할 상태와 상태 변경 함수를 store로 정의합니다.
import { create } from "zustand";
const useStore = create((set) => ({ count: 0, increase: () => set((state) => ({ count: state.count + 1 })), decrease: () => set((state) => ({ count: state.count - 1 })), })); |
상태와 상태 변경 로직을 한 곳에서 정의하며, 액션이나 리듀서를 따로 나누지 않아도 됩니다.
3. 컴포넌트에서 상태 사용하기
정의한 store는 React hook처럼 사용할 수 있습니다.
function Counter() { const count = useStore((state) => state.count); const increase = useStore((state) => state.increase);
return ( <div> <p>{count}</p> <button onClick={increase}>+</button> </div> ); } |
컴포넌트에서는 필요한 상태만 선택해서 사용할 수 있으며, 별도의 Provider 설정 없이 바로 적용할 수 있습니다.
React에서 Zustand 사용하기
1. store를 먼저 만든다
// stores/useCounterStore.ts import { create } from "zustand";
type CounterState = { count: number; increase: () => void; };
export const useCounterStore = create<CounterState>((set) => ({ count: 0, increase: () => set((state) => ({ count: state.count + 1 })), })); |
- 상태와 상태 변경 함수(액션)를 한 파일에 정의
- Provider, reducer, action 분리 없음
2. React 컴포넌트에서 hook처럼 사용한다
Zustand store는 React hook처럼 바로 사용할 수 있습니다.
import { useCounterStore } from "@/stores/useCounterStore";
function Counter() { const count = useCounterStore((state) => state.count); const increase = useCounterStore((state) => state.increase);
return ( <button onClick={increase}> count: {count} </button> ); } |
- useState 쓰듯 자연스럽게 사용
- 필요한 상태만 selector로 가져옴
- Provider로 감쌀 필요 없음
3. 여러 상태가 있어도 필요한 것만 가져온다
| const count = useCounterStore((s) => s.count); |
- 다른 상태가 바뀌어도 이 컴포넌트는 영향 없음
- 불필요한 리렌더링이 줄어듦
4. React 생명주기와 함께 사용하기
비동기 로직이나 초기 호출도 일반 React 패턴 그대로 사용합니다.
function Page() { const fetchData = useStore((s) => s.fetchData);
useEffect(() => { fetchData(); }, [fetchData]);
return <div>...</div>; } |
Zustand가 React의 흐름을 바꾸지 않습니다.
Next.js에서 Zustand 사용하는 방법
Next.js에서도 React와 사용 방법은 거의 동일합니다. SSR/CSR 환경과 관련해 몇 가지 더 신경 써야 할 점이 있습니다.
1. 기본 사용법은 React와 동일
Next.js에서도 Zustand는 hook처럼 그대로 사용합니다.
// stores/useCounterStore.ts import { create } from "zustand";
export const useCounterStore = create((set) => ({ count: 0, increase: () => set((s) => ({ count: s.count + 1 })), })); tsx Copy code "use client";
import { useCounterStore } from "@/stores/useCounterStore";
export default function Counter() { const count = useCounterStore((s) => s.count); const increase = useCounterStore((s) => s.increase);
return <button onClick={increase}>{count}</button>; } |
- Provider 설정 불필요
- selector 사용 방식 동일
- React와 사용법 차이 없음
2. App Router에서는 반드시 "use client"
Next.js(App Router)에서는 Zustand를 사용하는 컴포넌트가 Client Component여야 합니다.
| "use client"; |
이 선언이 없으면 Zustand hook을 사용할 수 없습니다.
- store 파일에는 필요 없음
- Zustand를 호출하는 컴포넌트에만 필요
3. SSR 환경에서 주의할 점
Zustand store는 보통 모듈 스코프에서 생성됩니다. 이 상태로 서버 사이드 렌더링에서 사용하면, 이론적으로 요청 간 상태가 공유될 가능성이 있습니다. 이 때문에 Next.js 환경에서는 상태 관리 범위를 명확히 나누는 것이 중요합니다.
- UI 상태, 클라이언트 전용 전역 상태만 Zustand로 관리
- 요청마다 달라지는 서버 데이터는 fetch, React Query, SWR 등으로 분리
// 적합한 예 modalOpen, tab, theme
// 주의가 필요한 예 userList, postList (SSR 환경)
|
4. persist 사용 시 Next.js에서 주의
localStorage는 브라우저 환경에서만 존재합니다. 따라서 persist 미들웨어는 클라이언트에서만 정상적으로 동작합니다.
import { persist } from "zustand/middleware";
export const useStore = create()( persist( (set) => ({ theme: "light", setTheme: (t) => set({ theme: t }), }), { name: "theme" } ) ); |
- App Router에서는 Client Component에서만 접근
- 초기 렌더 시점과 저장된 상태 반영 타이밍에 따라 hydration 이슈가 발생할 수 있어 주의 필요
Zustand, 이런 프로젝트에서 잘 맞습니다

Zustand는 모든 프로젝트에 범용적으로 적용하기 위한 상태 관리 도구라기보다는, 프로젝트의 성격과 개발 단계에 따라 강점을 발휘하는 선택지에 가깝습니다. 상태 관리는 필요하지만, 구조적 부담은 최소화하고 싶은 경우에 특히 잘 어울립니다.
서비스형 웹 애플리케이션 프로젝트
사용자 상태, UI 상태, 공통 데이터처럼 전역으로 관리해야 할 값이 점점 늘어나는 서비스형 웹 프로젝트에 적합합니다. 전자상거래, 콘텐츠 플랫폼, SaaS 서비스처럼 화면과 기능이 확장되는 과정에서 상태 관리를 단순하게 유지하고 싶은 경우에 효과적으로 활용할 수 있습니다.
빠른 MVP · 프로토타입 개발 프로젝트
기획과 구현이 빠르게 반복되는 초기 단계의 프로젝트에서는 상태 관리 도구의 준비 과정이 곧 개발 속도에 영향을 미칩니다. Zustand는 별도의 구조 설계 없이 바로 적용할 수 있어, MVP나 프로토타입 개발 과정에서 전역 상태를 빠르게 정리하는 데 도움이 됩니다.
상태 관리 복잡도는 있지만 Redux까지는 필요 없는 프로젝트
Context API만으로는 상태 흐름을 관리하기 어려워졌지만, Redux의 규칙과 구조는 과하다고 느껴지는 단계에 잘 맞습니다. 필요한 상태만 선택적으로 관리하면서, 상태 변경 로직을 간결하게 유지할 수 있어 실무 부담을 줄일 수 있습니다.
프론트엔드 개발 생산성을 중시하는 프로젝트
상태 관리 자체보다 기능 구현과 사용자 경험 개선에 집중해야 하는 프로젝트에 적합합니다. 상태 관리 코드가 과도하게 늘어나지 않아 전체 코드베이스를 이해하기 쉽고, 변경 사항을 빠르게 반영할 수 있는 구조를 유지하는 데 도움이 됩니다.
Zustand를 도입할 때 고려해야 할 점

Zustand는 가볍고 적용이 쉬운 상태 관리 도구지만, 도입 전에 몇 가지 기준을 정리해두면 시행착오를 줄일 수 있습니다. 특히 프로젝트가 커질수록 상태 관리 도구 자체보다, 상태를 어떻게 설계하고 어디까지 맡길지가 더 중요해집니다.
상태의 범위를 먼저 정해야 합니다
Zustand는 전역 상태를 만들기 쉽기 때문에, 모든 상태를 store로 올리고 싶어질 수 있습니다. 하지만 컴포넌트 내부에서 끝나는 UI 상태까지 전부 전역화하면 오히려 관리 대상이 늘어날 수 있습니다. 전역으로 관리할 상태와 로컬로 두는 상태의 기준을 먼저 정해두는 편이 안정적입니다.
- 여러 화면에서 공유되는 상태만 store로 올리기
- 특정 페이지에서만 쓰는 값은 컴포넌트 state로 유지
- 전역화가 ‘편의’인지 ‘필요’인지 기준 잡기
서버 데이터는 역할을 분리하는 것이 안전합니다
Zustand로도 서버 데이터를 담을 수는 있지만, 캐싱과 동기화, 재검증 같은 요구가 붙는 순간 관리 부담이 커질 수 있습니다. Next.js 환경에서는 특히 SSR과 섞일 수 있어, 서버 데이터는 fetch, React Query, SWR 같은 데이터 패칭 도구로 분리하는 방식이 더 안정적입니다.
- 서버 데이터는 데이터 패칭 도구에 맡기기
- Zustand는 UI 상태와 클라이언트 전역 상태에 집중
SSR이 섞이는 경우 상태 공유 가능성 점검
store가 커질수록 구조를 나누는 기준이 필요합니다
Zustand는 처음엔 store 하나로도 충분하지만, 기능이 늘어나면 한 store가 비대해지기 쉽습니다. 이때부터는 store를 어떻게 분리할지, 혹은 slice 패턴처럼 기능 단위로 나눌지 기준이 필요합니다. 상태 설계가 늦어지면 ‘가볍게 시작한 도구가 오히려 관리 부담이 되는 상황’이 다시 반복될 수 있습니다.
- 도메인 단위로 store를 분리할지 결정
- 기능 단위로 slice를 구성해 유지보수성 확보
- 상태와 액션 네이밍 규칙을 미리 통일
렌더링 영향을 의식하고 selector를 습관화해야 합니다
Zustand는 필요한 상태만 선택적으로 구독할 수 있다는 장점이 있지만, 이를 제대로 활용하려면 selector 사용을 기본 습관으로 가져가는 것이 좋습니다. 상태를 통째로 구독하거나 여러 값을 한 번에 묶어 가져오는 방식이 늘어나면, 의도치 않은 리렌더링이 발생할 수 있습니다.
- 필요한 상태만 selector로 가져오기
- 여러 값을 함께 쓸 땐 shallow 비교 고려
- 성능 이슈는 “도구”보다 “사용 방식”에서 발생
persist 사용 시 초기 렌더 타이밍을 점검해야 합니다
persist는 설정 값이나 사용자 환경 같은 상태를 저장하기에 유용하지만, 브라우저 환경에서만 동작합니다. Next.js에서는 Client Component에서만 접근해야 하고, 초기 렌더 시점과 저장 상태 반영 타이밍에 따라 화면이 순간적으로 바뀌는 현상이 생길 수 있어 UI 초기 상태 전략을 함께 고민하는 편이 좋습니다.
- 저장이 필요한 상태만 최소 범위로 persist
- 초기 렌더에서 보여줄 기본값을 명확히 정의
- 클라이언트에서 상태가 반영되는 타이밍 고려
상태 관리, 프론트엔드에서
‘필요한 만큼’이 가장 어려운 영역입니다.
상태 관리는 화면의 동작과 사용자 경험을 직접적으로 좌우하기 때문에 프론트엔드에서 중요한 역할을 합니다.
동시에 애플리케이션이 커질수록 상태의 범위와 책임이 늘어나면서, 어디까지 관리해야 하는지 판단하기가 점점 어려워집니다.
이러한 맥락에서 Zustand는 상태 관리의 복잡함을 줄이고, 필요한 만큼만 관리할 수 있도록 돕는 실무적인 상태 관리 도구가 될 것입니다.