Stop Using Zustand Like Redux (You're Killing Performance)
Zustand is tiny and fast until you force Redux habits into it. Use less global state, tighter selectors, and better store boundaries.
Zustand is great because it is simple.
But many teams bring broad-subscription patterns from older Redux codebases. That is where performance starts to slip.
If every UI concern goes into one global store, you lose most of what makes Zustand efficient.
1. Stop Promoting Local State to Global by Default
A lot of state does not need to be shared app-wide.
Examples:
- modal open/close
- input focus
- temporary form fields
- hover state
When this lives in a global store, components often subscribe too broadly and re-render for no product reason.
Rule of thumb:
- Use component state for UI-local behavior.
- Use Zustand only for genuinely shared, cross-tree state.
Global is not better by default. It simply increases the blast radius when subscription boundaries are loose.
2. Selectors Are Not Optional
This is one of the biggest mistakes:
const store = useAppStore()
That subscribes the component to the entire store object. Any state update can trigger re-render for that subscribed component.
Prefer scoped subscriptions:
const user = useAppStore((s) => s.user)
const updateUser = useAppStore((s) => s.updateUser)
Now the component re-renders only when selected slices change.
If you select objects or arrays, keep references stable or use shallow comparison where appropriate. Otherwise, you accidentally create new references and trigger renders anyway.
3. Poor Store Shape Creates Re-Render Chains
One giant "app store" feels convenient early. Later it often becomes a re-render machine.
Symptoms:
- frequently changing and rarely changing values mixed together
- broad selectors everywhere
- updates that touch too many subscribers
Split by domain and update frequency, not by file size. But do not over-split blindly, because cross-store updates and consistency can get harder to reason about.
Examples:
- session/auth store
- cart/checkout store
- realtime UI state store
You are not trying to create "one source of truth object." You are designing subscription boundaries.
4. Colocate First, Lift Later
Redux habits often reverse this: "make it global now, maybe useful later."
That usually adds complexity without payoff.
Better flow:
- start with local component state
- lift to nearest common parent when sharing is needed
- move to Zustand only when state must cross distant branches or persist beyond a local subtree or layout boundary
This keeps your render graph tighter and your mental model cleaner.
Practical Checklist
Before adding something to Zustand, ask:
- Is this state truly shared across distant components?
- Does it need to outlive a single route or component subtree?
- Can I subscribe to a small slice instead of the full store?
- Am I creating unnecessary object/array references in selectors?
If most answers are "no", keep it local.
Final Point
Zustand is not "Redux-lite." It is a small subscription-based state tool.
Used with local-first thinking, selectors, and clean store boundaries, it stays fast. Used like a global dumping ground with broad subscriptions, it will punish render performance.