| name | nextjs-zustand |
| description | Comprehensive Next.js + Zustand state management patterns and best practices. Use when building Next.js applications with Zustand for (1) Setting up Zustand stores in Next.js App Router or Pages Router, (2) Implementing state management patterns (slices, middleware, persistence), (3) Server-side rendering (SSR/SSG) with Zustand, (4) Performance optimization and code splitting, (5) TypeScript integration, (6) DevTools and debugging setup, or (7) Testing Zustand stores |
Next.js + Zustand Best Practices
This skill provides production-ready patterns for integrating Zustand state management in Next.js applications.
Core Concepts
Zustand Store Structure
Create stores with clear separation of concerns:
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
interface UserState {
user: User | null
isLoading: boolean
setUser: (user: User | null) => void
fetchUser: () => Promise<void>
}
export const useUserStore = create<UserState>()(
devtools(
persist(
(set) => ({
user: null,
isLoading: false,
setUser: (user) => set({ user }),
fetchUser: async () => {
set({ isLoading: true })
set({ isLoading: false })
},
}),
{ name: 'user-storage' }
)
)
)
Next.js App Router Integration
Client Component Pattern:
'use client'
import { useUserStore } from '@/stores/useUserStore'
export default function UserProfile() {
const user = useUserStore((state) => state.user)
const setUser = useUserStore((state) => state.setUser)
return <div>{user?.name}</div>
}
SSR Hydration Pattern:
import { HydrationBoundary } from '@/components/HydrationBoundary'
export default function RootLayout({ children }) {
return (
<html>
<body>
<HydrationBoundary>{children}</HydrationBoundary>
</body>
</html>
)
}
Store Slices Pattern
For large applications, split stores into slices:
export const createUserSlice = (set) => ({
user: null,
setUser: (user) => set({ user }),
})
export const createCartSlice = (set) => ({
items: [],
addItem: (item) => set((state) => ({ items: [...state.items, item] })),
})
export const useAppStore = create()((...a) => ({
...createUserSlice(...a),
...createCartSlice(...a),
}))
Performance Optimization
Selector Optimization
Use shallow equality for object selectors:
import { shallow } from 'zustand/shallow'
const { user, isLoading } = useUserStore()
const { user, isLoading } = useUserStore(
(state) => ({ user: state.user, isLoading: state.isLoading }),
shallow
)
Split Stores for Better Performance
Create separate stores for different domains:
export const useUserStore = create()
export const useCartStore = create()
export const useUIStore = create()
SSR/SSG Patterns
Initialize Store from Server Data
import { UserClient } from './UserClient'
export default async function Page() {
const userData = await fetchUserData()
return <UserClient initialData={userData} />
}
'use client'
export function UserClient({ initialData }) {
const initializeUser = useUserStore((state) => state.setUser)
useEffect(() => {
if (initialData) initializeUser(initialData)
}, [initialData, initializeUser])
return <UserProfile />
}
Middleware Patterns
Common Middleware Setup
import { create } from 'zustand'
import { devtools, persist, subscribeWithSelector } from 'zustand/middleware'
export const useStore = create<State>()(
devtools(
persist(
subscribeWithSelector((set) => ({
})),
{ name: 'app-storage' }
)
)
)
Custom Middleware Example
const logMiddleware = (config) => (set, get, api) =>
config(
(...args) => {
console.log('State before:', get())
set(...args)
console.log('State after:', get())
},
get,
api
)
TypeScript Best Practices
Typed Store Creation
import { create } from 'zustand'
import type { StateCreator } from 'zustand'
interface State {
count: number
increment: () => void
}
const createStore: StateCreator<State> = (set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
})
export const useStore = create<State>()(devtools(persist(createStore, { name: 'store' })))
Typed Selectors
import { useStore } from '@/stores/useStore'
export const useCount = () => useStore((state) => state.count)
export const useIncrement = () => useStore((state) => state.increment)
Testing
Unit Testing Stores
import { renderHook, act } from '@testing-library/react'
import { useUserStore } from '@/stores/useUserStore'
describe('useUserStore', () => {
beforeEach(() => {
useUserStore.setState({ user: null })
})
it('should set user', () => {
const { result } = renderHook(() => useUserStore())
act(() => {
result.current.setUser({ id: '1', name: 'Test' })
})
expect(result.current.user).toEqual({ id: '1', name: 'Test' })
})
})
Advanced Patterns
For detailed implementation guides, see:
Quick Reference
Installation:
npm install zustand
npm install zustand react
Basic Store:
import { create } from 'zustand'
export const useStore = create((set) => ({
data: null,
setData: (data) => set({ data }),
}))
With Persist:
import { persist } from 'zustand/middleware'
export const useStore = create(
persist((set) => ({ }), { name: 'storage-key' })
)
Selector:
const data = useStore((state) => state.data)