Learning Zustand - Production-Ready Architecture
Episode 21 of 23

Learning Zustand - Production-Ready Architecture

This episode covers production-ready store architecture: a consistent folder structure, slices patterns per feature, action naming conventions, and per-domain code-splitting. You also build a quality pipeline with linting, type-checking, test coverage for critical stores, and documentation for onboarding.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

A store that works on your laptop doesn't necessarily work in a team of twenty. Episode 21 covers production-ready store architecture: a consistent folder structure, slices patterns per feature, action naming conventions, and per-domain code-splitting. We also build a quality pipeline — linting, type-checking, test coverage for critical stores, and documentation for onboarding.

All these patterns are practical and can be adapted directly to your project.

Good store architecture isn't about the number of files, but about consistency: anyone on the team must be able to find a store, understand its responsibility, and add features without disturbing other domains.

Store Folder Structure

Start with a folder structure that makes store files easy to find:

Store folder structure
src/stores/
  auth.store.ts
  cart.store.ts
  ui.store.ts
  slices/
    user.slice.ts
    preferences.slice.ts
  middleware/
    logger.ts
  index.ts

The src/stores folder holds all stores with file names like domain.store.ts. Slices and custom middleware go in subfolders so the main store stays short and focused on combining, not on content.

The .store.ts suffix distinguishes store files from other files at a glance and makes editor globbing easy. Small consistencies like this keep a large codebase navigable.

Slices Pattern per Feature

One store per domain, and large domains are split with the slice pattern from episode 16. Each feature holds one slice: user, preferences, notifications. The main store only combines:

JSStore per feature
export const useStore = create<AppState>()((...a) => ({
  ...createUserSlice(...a),
  ...createPreferencesSlice(...a),
}))

createUserSlice(...a) and the other slices are written in separate files with a single responsibility. The rule: if two slices never need each other, consider making them separate stores.

Separately written slices are also easier to review: each pull request diff only touches one domain, so cross-feature conflicts are rare and responsibilities stay clear.

Action Naming Conventions

Consistent action names make review and code search far easier. Adopt the following conventions:

  • Mutation actions start with a verb: setTheme, addItem, clearCart.
  • Actions that load data end with the source: loadUser, fetchProducts.
  • Predicates for reads: isLoggedIn, hasItems, totalPrice.
  • Avoid generic names like update or set unless you're storing a field directly.

setTheme(theme) makes its purpose clear compared to update('theme', theme). These conventions make code grep and PR review much faster.

As a guideline, an action name should be able to complete a simple sentence: setTheme sets the theme, loadUser loads the user, addItem adds an item. If an action can't be explained in one short sentence, it's probably doing too much.

Store Code-Splitting per Domain

A bundle that carries all stores in the entry point makes the application slow to load. Split large stores into independent modules, then import them only when needed:

Dynamic store import
const useEditorStore = (await import('./editor.store')).useEditorStore

With lazy loading, a large editor store is only loaded when the editor feature is opened. For React-side code, combine with React.lazy and Suspense so store modules split along with feature chunks.

Before deciding what to split, measure the bundle first. Libraries like webpack-bundle-analyzer show each module's size — focus splitting on stores that are large and rarely opened, not on small stores that are always used.

Deployment and Quality

Linting and Type-Checking

Make linting and type-checking part of CI, not a personal habit. In a Next.js project, run:

Lint and type-check
bun run lint
bun run typecheck

bun run lint and bun run typecheck make sure stores don't carry type errors or convention violations into production. Block merges when either fails in the pipeline.

Also add linting at pre-commit or pre-push so mistakes are caught before reaching review. Fast feedback reduces waiting time and keeps quality at the cheapest point to fix.

Test Coverage and Documentation

Critical stores — auth, cart, permission — must have test coverage. Write unit tests like in episode 18 for actions and middleware. Document store contracts with short JSDoc: the state held, the actions provided, and the persist side effects. This documentation speeds up onboarding and reduces miscommunication between teams.

Coverage doesn't have to be 100 percent; focus on stores that touch money, security, and user data. To make sure stores are measured, run coverage only for the stores folder:

Check coverage of critical stores
bun vitest run --coverage --include src/stores

bun vitest run --coverage --include src/stores limits the report to store modules, so the coverage number reflects the domain that most needs testing.

Closing

Episode 21 lays out team-ready store architecture: a consistent folder structure, slices per feature, clear action naming conventions, per-domain code-splitting, and a quality pipeline from linting, type-checking, and test coverage to onboarding documentation.

Good architecture is a long-term investment: the consistency you apply now will pay off as the team grows and features multiply.

Key takeaways:

  • A consistent store folder structure makes code easier to find.
  • The slices pattern splits large stores per feature.
  • Action names starting with verbs speed up review.
  • Code-splitting loads a store only when its feature opens.
  • Linting and type-checking must run in CI.
  • Test coverage and documentation speed up team onboarding.

In the next episode we will discuss the alternative ecosystem and final reflection — comparing Zustand with Jotai, Valtio, Redux Toolkit, Context API, and URL state, building a decision framework for 2026, and recapping the journey of the entire series from episode 0 to 21.

Learning Zustand - Production-Ready Architecture | Learning Zustand