How I choose
When I use each
- The app is new, or the feature is small to medium
- The state is a handful of flags, preferences, or UI
- I want one file and no Provider around the tree
- The team is large and already standardized on Redux: consistency matters more than my preference.
- We need advanced DevTools: time-travel and action replay can be worth the cost.
- We have complex middleware needs: sagas, strict logging, or very specific side-effect pipelines.
- The product is an existing Redux codebase: rewriting for Zustand is not always justified.
Zustand vs Redux
Those sizes are the published packages on Bundlephobia for Zustand 5.0.15 and Redux Toolkit 2.12.0, not a measurement of one of my production bundles. react-redux is a separate peer of Redux Toolkit and is not included in that 13.3 kB.
What shipping both taught me
Why I used Redux in the first place
- Predictable state updates with a single store and clear data flow
- DevTools for time-travel debugging and inspecting every action
- Middleware for side effects (API calls, logging)
- Ecosystem and patterns that many developers already know
What started to bother me
Why Zustand fit the smaller apps
- Minimal boilerplate: One file, one function, and you have a store.
- No provider: You create a store and use it. No wrapping the app.
- Simple API: getState(), setState(), and a useStore hook. Easy to read and teach.
- TypeScript-friendly: Typing the store is straightforward.
- Small published size: 489 bytes gzipped for version 5.0.15, from the Bundlephobia link above.
- Flexible: Use it for global state, or scope it to a part of the tree.
Migrating from Redux to Zustand
- Start at the edges: Pick one slice (UI or feature flags) and replace it with a Zustand store. Keep Redux for the rest.
- Match the shape: Design the Zustand store so components can switch with minimal changes (same field names, similar “selectors” via the hook).
- Split by domain: Use multiple small stores (useUIStore, useAuthStore) instead of one giant store. It keeps things readable and avoids unnecessary re-renders.
- Use selectors: Zustand’s useStore(selector) only re-renders when the selected slice changes, similar to useSelector. Use it for performance.

