This episode covers Redux DevTools integration through the devtools middleware with time-travel and action tracing, store name configuration, and the redux middleware for using Redux-style reducer patterns inside Zustand.

Watching state change in real time is the key to debugging. Episode 10 connects Zustand to Redux DevTools, the browser extension that gives you time-travel and action tracing. We also cover the redux middleware for teams that want the Redux-style reducer mental model without leaving Zustand.
These two middleware bridge Zustand to the debugging ecosystem and the older patterns many teams are already familiar with.
Install the Redux DevTools extension in your browser, then wrap the store with devtools:
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
interface CounterState {
count: number
increment: () => void
}
export const useCounter = create<CounterState>()(
devtools(
(set) => ({
count: 0,
increment: () => set((s) => ({ count: s.count + 1 })),
}),
{ name: 'CounterStore' },
),
)devtools(initializer, { name: 'CounterStore' }) sends every set as an action to the extension. In the Redux DevTools panel, you see a list of actions labeled with the store name, the state before and after, and the change diff.
The name option separates multiple stores in the same panel:
devtools(initializer, { name: 'AuthStore' })
devtools(initializer, { name: 'CartStore' }){ name: 'AuthStore' } gives each store a unique label in devtools. Zustand also sends metadata about the state-creating function, so actions can be traced back to their source in the code — a debugging convenience classic Redux doesn't have without extra setup.
With devtools enabled, the jump button in the extension rewinds state to any point in history. Zustand supports this time-travel natively through the devtools middleware — you can replay a series of actions to understand how a bug emerged.
Info
Redux DevTools runs synchronously and stays active in production if the middleware is installed. Consider enabling devtools only in development so production performance isn't affected.
For teams comfortable with reducers, Zustand provides the redux middleware:
import { create } from 'zustand'
import { redux } from 'zustand/middleware'
type Action = { type: 'inc'; payload?: number } | { type: 'dec' }
function reducer(state: CounterState, action: Action) {
switch (action.type) {
case 'inc':
return { count: state.count + (action.payload ?? 1) }
case 'dec':
return { count: state.count - 1 }
default:
return state
}
}
const initialState: CounterState = { count: 0 }
export const useCounter = create(redux(reducer, initialState))create(redux(reducer, initialState)) accepts a pure reducer and an initial state. Dispatch actions via the dispatch provided by the store:
const dispatch = useCounter((s) => s.dispatch)
dispatch({ type: 'inc', payload: 2 })dispatch({ type: 'inc', payload: 2 }) follows the classic Redux pattern: a single source of truth for state, pure testable reducers, and typed actions. Notice there's no Provider or separate store configuration.
The redux middleware answers specific team needs:
If none of those needs apply, keep using plain set/get — it's more concise and idiomatic. redux is a bridge tool, not the default replacement.
Both can be combined: wrap redux with devtools so the reducer pattern stays debuggable:
export const useCounter = create<CounterState>()(
devtools(redux(reducer, initialState), { name: 'CounterRedux' }),
)devtools(redux(reducer, initialState), ...) gives you a pure reducer plus time-travel at once. The middleware order determines the store's shape — the outer wrapper is processed last during updates.
Episode 10 connects Zustand to the Redux ecosystem: the devtools middleware for time-travel and action tracing in the browser extension, and the redux middleware for using Redux-style reducers in a single store without a Provider.
Key takeaways:
In the next episode we will discuss advanced persistence and rehydration — custom storage with createJSONStorage for sessionStorage and AsyncStorage, serialization, versioning plus migrate for old state, and handling state that fails to rehydrate.