I built a time-travel debugger for Zustand — and it caught three bugs I'd already shipped
I built a time-travel debugger for Zustand — and it caught three bugs I’d already shipped
我为 Zustand 构建了一个时间旅行调试器——它甚至抓住了三个我已经发布到生产环境的 Bug
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.
如果你使用 Zustand,一定有过这样的时刻:UI 显示错误,你知道某个 Store 在不该变动时变动了,于是你开始到处添加 console.log 并不断刷新页面,试图捕捉那个导致问题的动作。状态调试就像侦探工作,而 Zustand 的设计初衷就是极简,它并没有提供太多现成的调试工具。我希望能直观地看到状态变化,所以我构建了 Zustand DevTools:一个 Chrome DevTools 面板,它能记录应用中的每一次状态变更,并允许你回溯查看。
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.
问题在于:Zustand 的核心魅力在于其小巧且不强制规范——没有样板代码,没有 Context 包装器,也没有繁琐的仪式感。但硬币的另一面是,它没有内置机制来回答“什么变了、何时变了、是因为哪个动作变了?”虽然存在 Redux DevTools 中间件,但它是为 Redux 设计的:以 Action 为中心,一旦你有多个小 Store 而不是一个大 Store 时,用起来就很别扭。我在调试时真正想要的是枯燥但具体的功能:每个 Store 当前状态的实时视图、每次变更的时间轴(标注动作名称)、精确到路径的变更差异(例如 items[1].quantity: 1 → 2,而不是让我肉眼去对比整个对象),以及能够跳转回任意时间点进行查看的能力。
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.
它的功能分为两部分。Stores + Timeline(免费):打开 DevTools,点击 Zustand 标签页,每个注册的 Store 都会实时显示。每一次 set() 都会成为时间轴上的一个条目,包含动作名称和路径级的差异对比:[cart] addItem items[1].quantity: 1 → 2 total: 168 → 297。点击任意条目即可时间旅行回那一刻——通过 ID 安全跳转,而不是通过重放脆弱的动作序列。
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.
Trace Sessions(专业版):在复现 Bug 时进行录制,然后检查录制内容:查看路径级差异、每次变更可能的调用位置、对比任意两个条目,并导出脱敏后的会话,供队友导入并以只读模式查看。导出功能是我用得最多的部分。“这是录制文件,把进度条拖到第 4 步,你就能看到总数在哪里出错了”,这比写一大段 Slack 消息有效得多。
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.)
如何配置:为你想要检查的每个 Store 添加一个包装器: (代码示例见上文) 只需为 Store 注册一个稳定的名称,从那以后你就能获得准确的实时视图。(还有一个无需配置的实验性视图,可以直接读取 Hook 进行快速查看——它有明确标注,因为它并不总能区分 Zustand 状态和其他 Hook。)
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.
我关心的两个设计决策:它采用链式调用 DevTools Hook,而不是覆盖它。真正的 React DevTools 可以与其并存,无论谁先加载。一个会破坏其他调试工具的调试工具,比没有工具更糟糕。状态永远不会离开你的机器。数据只从被检查的页面流向你的面板,不会流向其他任何地方——没有服务器、没有分析、没有遥测。你的应用状态通常是页面上最敏感的信息;它不应该为了让你查看而向外部发送数据。
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.
坦诚地说:我通过指挥 AI 编程代理来构建软件,但我默认不信任它们的输出。架构、命名、测试策略和代码审查都是我亲自完成的。我在两个主要框架版本上编写了 96 个自动化测试,因为一个存在细微错误的工具是非常危险的——你最终会陷入调试调试器本身的困境。老实说:在我“完成”之后,我对自己的代码进行了专门的审查,发现了三个测试套件通过但实际存在的静默故障。我修复了它们并保留了差异记录。如果一个用于捕捉 Bug 的工具连作者自己的严格审查都经受不住,那它就不值得你的信任。
Try it: Free: install the Stores + Timeline tools from the Chrome Web Store, add zustand-devtools-bridge to 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/
试用:免费版:从 Chrome 网上应用店安装 Stores + Timeline 工具,将 zustand-devtools-bridge 添加到你的应用中,并包装一个 Store。专业版:一次性支付 9.99 欧元即可获得无限 Trace Sessions——提供三次完整预览,无订阅费,支持最多 5 台设备,30 天内退款。👉 试用地址: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. 🙏
如果你使用 Zustand 和 React,我真心想知道它有哪些不足之处——比如你尝试检查什么但它无法显示。正是这些反馈让它变得更好。附言:我是这里的新人,这是我在 dev.to 上的第一篇文章,老实说,像这样公开分享作品我还在摸索中。如果你对新人有什么建议(或者对这个工具有什么想法),我非常乐意倾听。🙏