Plate 93
Nanostores vs Jotai: 1.3 KB vs 2.7 KB Gzip
Aditya Challa4 min read
I bundled the atom APIs on this box: Nanostores 1.5.4 came in at 1,280 B gzip against Jotai 3.0.1 vanilla at 2,708 B. On a 200-atom listen+bump loop, Nanostores ran about 7.6M updates/s and Jotai about 582k.
Short answer: pick Nanostores when you want a tiny, framework-agnostic atom store. Pick Jotai when you already live in React and want a large atom ecosystem. No affiliate links in this post.
What I tested
- Machine: 8 vCPU Intel Xeon / 15 GB RAM shared Linux cloud box, tested 6 Oct 2026 about 00:55–00:57 IST.
- Versions: nanostores 1.5.4, jotai 3.0.1, @nanostores/react 2.0.1, React external for the React-binding size check, Node 22.20.0, esbuild 0.28.2 minify + gzip -9.
- Fixture: /workspace/bench-nanostores-jotai/. Vanilla size: nanostores atom+computed+onMount vs jotai/vanilla atom+createStore. React size: nanostores+useStore vs jotai atom+useAtomValue+useSetAtom+Provider with React external. Speed: 200 atoms each with one listener, 10,000 bumps × 9 rounds (median); plus a simple single-atom path and a two-atom derived path.
- Findings: gzip 1,280 B vs 2,708 B (vanilla); with React bindings 1,422 B vs 3,283 B. Median 200-atom loop 1.324 ms vs 17.177 ms for 10k bumps (about 7.56M vs 582k updates/s). Simple path about 10.6M vs 728k updates/s.
- One surprise: the derived-pair path widened the gap further (about 4.1M vs 156k updates/s). Nanostores stayed cheaper when a computed sat on top of two atoms.
- Not tested: jotai React Suspense helpers, atomFamily, Nanostores persistent/map stores, SSR hydration, real browsers, React Native, DevTools. This is not a re-run of jotai vs Zustand (different intent).
How wide was the size gap?
| Browser build (esbuild minify) | Minified | gzip -9 |
|---|---|---|
| nanostores (atom, computed, onMount) | 2,556 B | 1,280 B |
| jotai/vanilla (atom, createStore) | 6,339 B | 2,708 B |
| nanostores + @nanostores/react (React external) | 2,775 B | 1,422 B |
| jotai React exports (React external) | 7,513 B | 3,283 B |
Nanostores was about 2.1× smaller gzip on the vanilla imports and about 2.3× smaller with the React hook. Disk trees were about 192 KB for nanostores against 360 KB for jotai. Track shipped weight with size-limit vs bundlesize. Unused-export cleanup after a swap: knip vs depcheck.
How fast were the updates?
| Path (10,000 bumps, median of 9) | Nanostores | Jotai |
|---|---|---|
| 200 atoms + 1 listener each | 1.324 ms (7.56M/s) | 17.177 ms (582k/s) |
| Single atom + 1 listener | 0.944 ms (10.6M/s) | 13.737 ms (728k/s) |
| Derived pair (A+B→C) | 2.414 ms (4.14M/s) | 64.233 ms (156k/s) |
On this box Nanostores was roughly an order of magnitude faster on the multi-atom listen path. Framework harness notes sit next to my jsdom vs happy-dom write-up when I pull React into the loop.
Which one should you pick?
| Situation | My pick |
|---|---|
| Astro / Solid / Vue / multi-framework UI | Nanostores |
| React-only app that already uses atoms | Jotai |
| Bundle budget under about 2 KB gzip for atoms | Nanostores |
| You need a deep React atom ecosystem | Jotai |
| Tiny store shared across islands | Nanostores |
Bottom line: Nanostores 1.5.4 shipped 1.3 KB gzip against Jotai 3.0.1 vanilla at 2.7 KB, and ran about 7.6M updates/s against 582k on a 200-atom fixture. Who should not switch: teams already deep in Jotai atoms and React helpers — rewrite cost beats a 1.4 KB gzip win. Lint the swap with Biome vs ESLint/Prettier. Test runner: Jest vs Vitest.
How this was made: I installed Nanostores and Jotai on the ShopperCove box, measured esbuild minify+gzip sizes (vanilla and React-external), timed store update loops, and recorded the JSON. The write-up was drafted with AI help and checked against that output.
Sources
- https://github.com/nanostores/nanostores
- https://www.npmjs.com/package/nanostores
- https://jotai.org/
- https://www.npmjs.com/package/jotai
Related
- https://www.shoppercove.com/blog/size-limit-vs-bundlesize
- https://www.shoppercove.com/blog/knip-vs-depcheck
- https://www.shoppercove.com/blog/jsdom-vs-happy-dom-vitest
- https://www.shoppercove.com/blog/jest-vs-vitest
- https://www.shoppercove.com/blog/biome-vs-eslint-prettier
- https://www.shoppercove.com/blog/oxlint-vs-eslint
- https://www.shoppercove.com/blog/lightningcss-vs-postcss
- https://www.shoppercove.com/blog/vite-vs-parcel
Lab evidence
What I found running this
Hands-on on ShopperCove box 6 Oct 2026 ~00:55-00:57 IST (8 vCPU Intel Xeon / 15 GB shared Linux, Node 22.20.0). nanostores 1.5.4 vs jotai 3.0.1, @nanostores/react 2.0.1, esbuild 0.28.2. Fixture /workspace/bench-nanostores-jotai/: vanilla entry atom+computed+onMount vs jotai/vanilla atom+createStore; React-external entries for useStore vs jotai hooks+Provider. Speed: 200 atoms with one listener each, 10,000 bumps x 9 rounds (median); simple single-atom; derived A+B→C. Medians: gzip 1,280 vs 2,708 B vanilla; 1,422 vs 3,283 B with React. 200-atom 1.324 vs 17.177 ms (7.56M vs 582k ups); simple 0.944 vs 13.737 ms; derived 2.414 vs 64.233 ms. Disk ~192 KB vs ~360 KB. Raw: res-bundle.json, res-store.json. Not tested: jotai Suspense/atomFamily, Nanostores persistent/map, SSR, real browsers, RN, DevTools. No affiliate. Distinct from jotai-vs-zustand (framework-agnostic atoms vs React store).
Related links
Plate 66
Jotai vs Zustand: 0.15 ms vs 0.17 ms per Update
5 Oct 2026
Plate 38
TanStack Query vs SWR: 10.4 KB vs 5.6 KB Gzip
5 Oct 2026
Plate 60
Zustand vs Redux Toolkit: 0.14 ms vs 0.68 ms per Update
zustand 5.0.15 vs @reduxjs/toolkit 2.13.0 + react-redux 9.3.0 on React 19.3.0 (jsdom, NODE_ENV=production). 1,000 selector-subscribed rows, 300 flushSync updates x 7 trials, 2 runs: per-update median 0.137/0.139 ms vs 0.676/0.694 ms (~5x); 1 row re-rendered per update in both. Store-only: 1,376,191 vs 6,502 updates/s; Immer autoFreeze off lifts RTK to 386,966. Gzip (React external): 387 B vs 9,708 B. Fixture /workspace/bench-zustand-rtk/.
5 Oct 2026