I built a time-travel debugger for Zustand — and it caught three bugs I'd already shipped
If you use Zustand, you've had this moment: the UI is wrong, you know a store changed when it shouldn't have, and now you're scattering console.log calls and refreshing, trying to catch the one action that did it. State
If you use Zustand, you've had this moment: the UI is wrong, you know a store changed when it shouldn't have, and now you're scattering console.log calls and refreshing, trying to catch the one action that did it. State debugging is detective work — and Zustand, by design, is so minimal that it doesn't give you much to work with.
I wanted to see it instead. So I built Zustand DevTools: a Chrome DevTools panel that records every state change in your app and lets you walk back through them.
The problem
Zustand's whole appeal is that it's tiny and unopinionated — no boilerplate, no context wrappers, no ceremony. The flip side is there's no built-in answer to "what changed, when, and because of which action?" The Redux DevTools middleware exists, but it's Redux-shaped: action-centric, and awkward the moment you have several small stores instead of one big one.
What I actually wanted while debugging was boring and specific:
- a live view of each store's current state,
- a timeline of every change, labelled with the action name,
- the exact path that changed (
items[1].quantity: 1 → 2), not a whole-object diff I have to eyeball, - and the ability to jump back to any point and look around.
What it does
Two parts.
Stores + Timeline (free). Open DevTools, click the Zustand tab, and every registered store shows up live. Every set() becomes a timeline entry with the action name and a path-level diff:
[cart] addItem items[1].quantity: 1 → 2 total: 168 → 297
Click any entry to time-travel to that moment — safely, by ID, rather than by replaying a fragile sequence of actions.
Trace Sessions (Pro). Record while you reproduce a bug, then inspect the recording: path-level diffs, the likely call-site of each change, compare any two entries, and export a redacted session a teammate can import and inspect view-only. That export is the part I use most. "Here's the recording — drag the playhead to step 4 and you'll see where the total goes wrong" beats a paragraph of Slack every single time.
How you wire it up
One wrapper per store you want to inspect:
import { create } from 'zustand';
import { withDevtoolsBridge } from 'zustand-devtools-bridge';
const useCartStore = create(
withDevtoolsBridge(
(set) => ({
items: [],
addItem: (item) =>
set((s) => ({ items: [...s.items, item] }), false, 'addItem'), // named in the log
}),
{ name: 'cart' }
)
);
Register a store once with a stable name and you get an accurate live view of it from then on. (There's also a zero-setup experimental view that reads hooks directly for a quick look — it's clearly labelled, because it can't always tell Zustand state apart from other hooks.)
Two design decisions I care about:
- It chains the DevTools hook instead of clobbering it. The real React DevTools keeps working right next to it, whichever loads first. A debugging tool that breaks your other debugging tools is worse than nothing.
- State never leaves your machine. Data flows from the inspected page to your panel and nowhere else — no server, no analytics, no telemetry. Your app state is often the most sensitive thing on the page; it shouldn't phone home just so you can look at it.
The honest part
I build software by directing AI coding agents, and I don't trust their output by default. The architecture, the naming, the test strategy and the review are mine. There are 96 automated tests across two major framework versions, because a state tool that's subtly wrong is actively dangerous — you'd end up debugging the debugger.
And to be straight: after I'd "finished," I did a dedicated review pass over my own code and found three silent failures a green test suite had missed. I fixed them and kept the diffs. If a tool for catching bugs can't survive its own author looking hard at it, it hasn't earned your trust.
Try it
-
Free: install the Stores + Timeline tools from the Chrome Web Store, add
zustand-devtools-bridgeto your app, and wrap a store. - Pro: unlimited Trace Sessions for a €9.99 one-time purchase — three full previews free, no subscription, up to 5 devices, 30-day refund.
👉 Try it: https://kubaopoczka.github.io/zustand-devtools-site/
If you use Zustand and React, I'd genuinely like to know where it falls short — what you tried to inspect that it couldn't show you. That's the feedback that makes it better.
P.S. — I'm new here: this is my first dev.to post, and honestly still finding my feet sharing work out loud like this. If you've got tips for a newcomer (or thoughts on the tool itself), I'd genuinely love to hear them. 🙏
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.