Architecting Zustand Stores at Scale
Why one big store collapses as your React app grows — and the store-per-domain pattern I use instead. With real examples from a 40+ screen production app.
Architecting Zustand Stores at Scale
Zustand is a pleasure for small apps. One create() call, a hook, done. But the pattern that makes it addictive — a single global store — does not survive contact with a real product. After shipping a 40+ screen app on it, here's what I'd do on day one if I were starting over.
The problem with one big store
Every component subscribes to the same store. Every action — even a loading spinner toggle on a modal three routes away — is a candidate cause for a re-render on your home screen. You paper this over with selector functions and shallow, but the mental model is still *everything depends on everything*.
Selector discipline works for a while. Then a new teammate adds set({ ...state, ... }) somewhere, and you're back to spaghetti.
Store per domain
The pattern that held up for me: one store per bounded context. Auth, feature-flags, the active document, the command palette — each gets its own tiny create(). They never import each other.
// stores/useAuthStore.ts
export const useAuthStore = create<AuthState>((set) => ({
user: null,
signIn: async (email, pw) => {
const user = await api.signIn(email, pw);
set({ user });
},
}));Each store is small enough to hold in your head. Tests mock one store at a time. When a domain is gone, you delete the file.
Cross-store coordination
When two stores need to talk, the coordinator lives *outside* the stores — usually in a hook or a saga-like async function. Stores stay dumb. Orchestration is explicit.
Rules I enforce in code review
- A store file is under 150 lines or it's too big.
- No store imports another store.
- Every action returns
voidorPromise<void>— never derived state. - Selectors are colocated with the component that uses them.
Closing
Zustand isn't Redux-lite; treat it that way and you'll reinvent every Redux footgun. Treat each store as a small, single-purpose module and it stays fun at scale.