Zustand vs Jotai vs Redux 비교 2026: React 상태관리, 뭘 써야 할까

Zustand vs Jotai vs Redux 비교 2026

React 상태관리 비교 2026

Zustand vs Jotai vs Redux: React 상태관리 라이브러리 완전 비교

경량화 vs 원자화 vs 강력함 — 2026년 팅패는 무엇인가?

포스트 대표 이미지

React 상태관리 라이브러리를 선택할 때 가장 많이 비교되는 세 가지가 바로 Zustand, Jotai, Redux Toolkit입니다. 10년 넘게 Redux를 써온 팀도 있고, 2년 전부터 Zustand로 갈아탄 팀도 있습니다. 2026년 기준으로 실제 프로젝트에서 세 가지를 모두 써본 경험을 바탕으로 정리합니다.

🔍 한눈에 보는 Zustand vs Jotai vs Redux 비교표

비교 항목 Zustand Jotai Redux Toolkit
번들 크기~1KB~3KB~11KB
학습 곡선매우 낮음 ✅낮음 ✅보통 ⚠️
보일러플레이트최소 ✅최소 ✅많음 ⚠️
상태 모델중앙 집중 Store원자(Atom) 단위중앙 집중 Store
리렌더링 최적화선택적 구독원자 단위 자동선택자 기반
TypeScript 지원우수 ✅우수 ✅우수 ✅
DevTools기본 지원Jotai DevToolsRedux DevTools ✅
미들웨어/플러그인immer, persist 등jotai-tanstack, zubridge 등RTK Query, Thunk ✅
비동기 처리직접 구현atomWithAsyncRTK Query / Thunk
추천 사용 규모소~중형소~중형중~대형
npm 주간 다운로드~9M~2.5M~10M+

📦 번들 크기 & 보일러플레이트 비교

번들 크기는 프론트엔드 성능에 직결됩니다. 특히 모바일 환경에서는 1KB 차이도 체감이 납니다.

라이브러리 minified+gzip 설치 패키지 수 카운터 앱 구현 코드 줄수
Zustand~1.1KB1개~8줄
Jotai~3.2KB1개~6줄
Redux Toolkit~11KB2개 (redux + toolkit)~25줄

🐻 Zustand 실전 코드

Zustand는 설정이 거의 없습니다. store를 하나의 훅으로 정의하고 바로 사용합니다.

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

interface CounterState {
  count: number
  increment: () => void
  decrement: () => void
  reset: () => void
}

export const useCounterStore = create<CounterState>()(set => ({
  count: 0,
  increment: () => set(state => ({ count: state.count + 1 })),
  decrement: () => set(state => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 }),
}))

// 컴포넌트에서 사용
export function Counter() {
  const { count, increment, decrement } = useCounterStore()
  return (
    <div>
      <button onClick={decrement}>-</button>
      <span>{count}</span>
      <button onClick={increment}>+</button>
    </div>
  )
}

선택적 구독으로 리렌더링을 제어할 수도 있습니다:

// count 값만 구독 — 다른 state 변경 시 리렌더링 없음
const count = useCounterStore(state => state.count)

// persist 미들웨어로 localStorage 동기화
import { persist } from 'zustand/middleware'

const useStore = create()(persist(
  (set) => ({ count: 0, increment: () => set(s => ({ count: s.count + 1 })) }),
  { name: 'counter-storage' }
))

⚛️ Jotai 실전 코드

Jotai는 React의 useState와 가장 비슷한 API를 제공합니다. 원자(atom) 단위로 상태를 정의하고, 필요한 곳에서 useAtom으로 사용합니다.

// atoms/counterAtom.ts
import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'

export const countAtom = atom(0)

// 파생 atom (computed state)
export const doubleCountAtom = atom((get) => get(countAtom) * 2)

// 컴포넌트에서 사용
export function Counter() {
  const [count, setCount] = useAtom(countAtom)
  const doubleCount = useAtomValue(doubleCountAtom)
  
  return (
    <div>
      <p>count: {count} / double: {doubleCount}</p>
      <button onClick={() => setCount(c => c + 1)}>+</button>
    </div>
  )
}

// atomWithStorage로 localStorage 연동
import { atomWithStorage } from 'jotai/utils'
const darkModeAtom = atomWithStorage('darkMode', false)

🔴 Redux Toolkit 실전 코드

Redux Toolkit은 보일러플레이트가 많지만, 대형 앱에서는 이 구조가 오히려 팀 협업에 유리합니다.

// store/counterSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit'

interface CounterState { value: number }

const initialState: CounterState = { value: 0 }

export const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increment: (state) => { state.value += 1 },
    decrement: (state) => { state.value -= 1 },
    incrementByAmount: (state, action: PayloadAction<number>) => {
      state.value += action.payload
    },
  },
})

export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer

// store/index.ts
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from './counterSlice'

export const store = configureStore({
  reducer: { counter: counterReducer }
})
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch

// 컴포넌트에서 사용
import { useDispatch, useSelector } from 'react-redux'

function Counter() {
  const count = useSelector((state: RootState) => state.counter.value)
  const dispatch = useDispatch<AppDispatch>()
  return <button onClick={() => dispatch(increment())}>{count}</button>
}

⚡ 성능 & 리렌더링 비교

시나리오 Zustand Jotai Redux Toolkit
단순 카운터 업데이트~0.1ms~0.1ms~0.2ms
1,000개 아이템 리스트 렌더링선택적 구독으로 우수atom 분리로 최적selector 튜닝 필요
글로벌 상태 업데이트 시 리렌더링 범위구독 컴포넌트만해당 atom 구독 컴포넌트만selector 없으면 전체
초기 번들 로드 영향거의 없음거의 없음약간 있음
메모리 사용량낮음낮음보통

🛠️ DevTools & 생태계 비교

항목 Zustand Jotai Redux Toolkit
DevTools 브라우저 확장Redux DevTools 연동 가능Jotai DevTools (별도 설치)Redux DevTools ✅ 최고
타임트래블 디버깅미들웨어로 가능제한적완벽 지원 ✅
React Native 지원
서버 컴포넌트(RSC) 지원클라이언트 전용클라이언트 전용클라이언트 전용
GitHub Stars (2026 기준)~51K~20K~11K (RTK) / redux ~61K

🎯 어떤 상황에 뭘 써야 할까?

🐻 Zustand를 선택하세요

  • 빠르게 MVP를 만들어야 할 때
  • 팀 전체가 Redux에 지쳐 있을 때
  • 상태 구조가 단순한 소~중형 앱
  • Next.js App Router와 함께 쓸 때
  • localStorage 동기화가 필요할 때

⚛️ Jotai를 선택하세요

  • 컴포넌트 단위 상태 관리가 필요할 때
  • 파생 상태(computed state)가 복잡할 때
  • React Suspense와 자연스럽게 쓰고 싶을 때
  • useState의 스케일업 버전이 필요할 때
  • 상태를 atom으로 분리해 테스트하고 싶을 때

🔴 Redux Toolkit을 선택하세요

  • 5인 이상 팀이 협업하는 대형 앱
  • 시간여행 디버깅이 필수일 때
  • RTK Query로 서버 상태 통합 관리
  • 레거시 Redux 코드베이스 유지보수
  • 엄격한 아키텍처 컨벤션이 필요할 때
항목
번들 크기매우 작음 (~2KB)작음 (~4KB)크다 (~20KB)
학습 곡선매우 낮음낮음높음
성능우수우수좋음
DevTools기본 지원설정 필요강력함
타입 안정성좋음우수우수
미들웨어간단간단복잡
에코시스템성장 중새로움매우 큼

🏆 최종 결론

2026년 Jake의 추천

신규 프로젝트 · 스타트업 · 1~4인 팀Zustand
API가 가장 단순하고, 생산성이 뛰어납니다. 99%의 케이스에서 충분합니다.

복잡한 파생 상태 · React Suspense 활용Jotai
atom 모델이 복잡한 상태 의존 관계를 깔끔하게 정리해줍니다.

5인 이상 팀 · 엔터프라이즈 · 레거시 코드Redux Toolkit
DevTools, 타임트래블, RTK Query가 대형 앱에서 진가를 발휘합니다.

솔직히 말하면, 2026년 신규 프로젝트라면 저는 무조건 Zustand를 선택합니다. 배우는 데 30분도 안 걸리고, 나중에 복잡해지면 Redux로 마이그레이션하는 것보다 처음부터 Zustand로 시작해서 구조를 잡는 게 훨씬 빠릅니다.

❓ 자주 묻는 질문 (FAQ)

Q. Zustand와 Jotai를 같이 써도 되나요?

네, 가능합니다. 전역 앱 상태(테마, 인증)는 Zustand, 컴포넌트 간 공유 상태는 Jotai로 분리해 쓰는 팀도 있습니다. 다만 두 라이브러리가 혼재하면 팀 내 혼란이 생길 수 있으니, 명확한 사용 기준을 정하는 것이 중요합니다.

Q. Redux에서 Zustand로 마이그레이션하는 게 어렵나요?

생각보다 어렵지 않습니다. 핵심은 Redux의 slice를 Zustand의 create()로 1:1 변환하는 것입니다. RTK Query를 쓰고 있다면 TanStack Query로 교체하는 것을 함께 고려하세요. 한 번에 전체를 마이그레이션하기보다, 새 기능은 Zustand로 작성하고 기존 코드는 점진적으로 교체하는 방식을 추천합니다.

Q. Next.js App Router에서 상태관리 라이브러리를 써도 되나요?

Zustand, Jotai, Redux 모두 클라이언트 컴포넌트('use client')에서만 동작합니다. 서버 컴포넌트에서는 사용할 수 없으며, 서버 상태는 TanStack Query나 Next.js의 fetch 캐싱을 활용하는 것이 권장됩니다. Next.js App Router에서는 전역 클라이언트 상태를 최소화하는 방향으로 설계하는 것이 좋습니다.

Q. React Context와 비교했을 때 언제 상태관리 라이브러리가 필요한가요?

React Context는 자주 변경되지 않는 데이터(테마, 언어 설정, 인증 정보)에 적합합니다. 반면 초당 여러 번 업데이트되는 상태(폼, 실시간 데이터, 쇼핑카트)는 Context의 전체 리렌더링 문제가 성능 이슈로 이어집니다. 이 경우 Zustand나 Jotai가 훨씬 효율적입니다.

댓글