home / skills / fusengine / agents / nextjs-zustand

This skill helps you implement scalable Zustand v5 state management in Next.js 16 App Router for client components.

npx playbooks add skill fusengine/agents --skill nextjs-zustand

Review the files below or copy the command above to add this skill to your agents.

Files (13)
SKILL.md
5.2 KB
---
name: nextjs-zustand
description: Zustand v5 state management for Next.js 16 App Router. Use when implementing global state, stores, persist, hydration, or client-side state in Client Components.
versions:
  zustand: 5.0
  react: 19
  nextjs: 16
user-invocable: true
references: references/installation.md, references/store-patterns.md, references/hydration.md, references/middleware.md, references/nextjs-integration.md, references/typescript.md, references/slices.md, references/auto-selectors.md, references/reset-state.md, references/subscribe-api.md, references/testing.md, references/migration-v5.md
related-skills: nextjs-16, nextjs-tanstack-form, solid-nextjs
---

# Zustand for Next.js 16

Minimal, scalable state management with React 18+ useSyncExternalStore.

## Agent Workflow (MANDATORY)

Before ANY implementation, use `TeamCreate` to spawn 3 agents:

1. **fuse-ai-pilot:explore-codebase** - Analyze existing stores and state patterns
2. **fuse-ai-pilot:research-expert** - Verify latest Zustand v5 docs via Context7/Exa
3. **mcp__context7__query-docs** - Check middleware and TypeScript patterns

After implementation, run **fuse-ai-pilot:sniper** for validation.

---

## Overview

### When to Use

- Managing client-side state in Next.js App Router applications
- Need global state across Client Components only
- Persisting state to localStorage/sessionStorage
- Building UI state (modals, sidebars, theme, cart)
- Replacing React Context for complex state

### Why Zustand v5

| Feature | Benefit |
|---------|---------|
| Minimal API | Simple create() function, no boilerplate |
| React 18 native | useSyncExternalStore, no shims needed |
| TypeScript first | Full inference with currying pattern |
| Middleware stack | devtools, persist, immer composable |
| Bundle size | ~2KB gzipped, smallest state library |
| No providers | Direct store access, no Context wrapper |

---

## Critical Rules

1. **Client Components ONLY** - Never use Zustand in Server Components
2. **Context pattern for App Router** - Avoid global stores (request isolation)
3. **useShallow for arrays/objects** - Prevent unnecessary re-renders
4. **skipHydration with persist** - Required for SSR compatibility
5. **Currying syntax v5** - `create<State>()((set) => ({...}))`
6. **SOLID paths** - Stores in `modules/[feature]/src/stores/`

---

## SOLID Architecture

### Module Structure

Stores organized by feature module:

- `modules/cores/stores/` - Shared stores (theme, ui)
- `modules/auth/src/stores/` - Auth state
- `modules/cart/src/stores/` - Cart state
- `modules/[feature]/src/interfaces/` - Store types

### File Organization

| File | Purpose | Max Lines |
|------|---------|-----------|
| `store.ts` | Store creation with create() | 50 |
| `store.interface.ts` | TypeScript interfaces | 30 |
| `store-provider.tsx` | Context provider (App Router) | 40 |
| `use-store.ts` | Custom hook with selector | 20 |

---

## Key Concepts

### Store Creation (v5 Syntax)

Double parentheses required for TypeScript inference. Currying pattern ensures full type safety.

### Context-Based Stores

For Next.js App Router, wrap stores in Context to prevent request-sharing. Use `createStore` from `zustand/vanilla` with `useRef` for initialization.

### Middleware Composition

Stack middlewares: devtools → persist → immer. Order matters for TypeScript types.

### Hydration Handling

Use `skipHydration: true` with persist middleware. Manually rehydrate in useEffect to avoid SSR mismatches.

---

## Reference Guide

| Need | Reference |
|------|-----------|
| Initial setup | [installation.md](references/installation.md) |
| Store patterns | [store-patterns.md](references/store-patterns.md) |
| SSR/Hydration | [hydration.md](references/hydration.md) |
| Middleware | [middleware.md](references/middleware.md) |
| Next.js App Router | [nextjs-integration.md](references/nextjs-integration.md) |
| TypeScript | [typescript.md](references/typescript.md) |
| Slices pattern | [slices.md](references/slices.md) |
| Auto selectors | [auto-selectors.md](references/auto-selectors.md) |
| Reset state | [reset-state.md](references/reset-state.md) |
| Subscribe API | [subscribe-api.md](references/subscribe-api.md) |
| Testing | [testing.md](references/testing.md) |
| Migration v4→v5 | [migration-v5.md](references/migration-v5.md) |

---

## Best Practices

1. **Selector pattern** - Always use `useStore((s) => s.field)` for performance
2. **useShallow** - Wrap array/object selectors to prevent re-renders
3. **Separate stores** - One store per domain (auth, cart, ui, theme)
4. **Server data elsewhere** - Use TanStack Query for server state
5. **DevTools in dev only** - Wrap devtools in process.env check
6. **Partialize persist** - Only persist necessary fields, never tokens

---

## Forbidden Patterns

| Pattern | Reason | Alternative |
|---------|--------|-------------|
| Global stores in App Router | Request sharing between users | Context-based stores |
| Zustand in Server Components | No React hooks in RSC | Fetch data directly |
| Persisting auth tokens | Security vulnerability | httpOnly cookies |
| Without useShallow on objects | Excessive re-renders | `useShallow(selector)` |
| v4 syntax | TypeScript inference broken | v5 currying `create<T>()()` |

Overview

This skill provides a standardized Zustand v5 state management setup tuned for Next.js 16 App Router. It supplies TypeScript-safe store patterns, Client Component guidance, persist/hydration helpers, and a context-based approach to avoid request-sharing. Use it to implement minimal, performant global client state across modular features.

How this skill works

The skill scaffolds currying-based Zustand v5 stores, middleware stacks (devtools → persist → immer), and context wrappers suitable for the App Router. It enforces client-only usage, skipHydration for persist, and helper hooks (selectors, useShallow) to prevent unnecessary re-renders. It also recommends file layouts and initialization patterns using createStore from zustand/vanilla and useRef for safe per-request instantiation.

When to use it

  • Managing client-side UI or domain state in Next.js App Router projects
  • Sharing global state across Client Components without React Context boilerplate
  • Persisting selective state to localStorage/sessionStorage with safe hydration
  • Replacing complex React Context setups for performance and simplicity
  • Implementing modular, SOLID-aligned stores per feature

Best practices

  • Always use the v5 currying syntax: create<State>()((set) => ({})) for full TypeScript inference
  • Wrap stores in a Context for App Router to avoid request sharing; initialize with createStore and useRef
  • Use selector pattern: useStore(s => s.field) and useShallow for arrays/objects to avoid re-renders
  • Compose middleware in order (devtools → persist → immer) and enable devtools only in development
  • Persist only necessary fields (never store tokens); use skipHydration: true and rehydrate client-side in useEffect

Example use cases

  • A theme store for client-only theme toggles and persisted preference
  • UI store for modals, sidebars, and temporary UI flags across Client Components
  • Cart store that persists items to localStorage while avoiding SSR mismatch
  • Auth client state (non-sensitive flags only) managed per-request via Context-based store
  • Feature-scoped stores in modules/cart or modules/auth following SOLID folder layout

FAQ

Can I use Zustand stores in Server Components?

No. Zustand is for client-side state and React hooks should not be used in Server Components; use server data fetching or TanStack Query for SSR data.

How do I avoid hydration mismatches with persist?

Use persist with skipHydration: true and rehydrate manually inside a useEffect on the client, so SSR and client renders remain consistent.

Why wrap stores in Context for the App Router?

Wrapping prevents request-sharing between users on the server by creating a separate store instance per request while still exposing easy hooks to Client Components.