Zustand vs Jotai vs Nanostores: Lightweight State Management (2026)
Redux is overkill for most apps. In 2026, lightweight state libraries handle 95% of state management needs with a fraction of the complexity. Zustand, Jotai, and Nanostores lead the pack.
Quick Comparison
| Feature | Zustand | Jotai | Nanostores |
|---|---|---|---|
| Mental model | Store (like Redux-lite) | Atoms (like Recoil) | Atoms (framework-agnostic) |
| Bundle size | ~1.1KB | ~2.4KB | ~0.3KB |
| Framework | React | React | Any (React, Vue, Svelte, vanilla) |
| TypeScript | Excellent | Excellent | Good |
| DevTools | Redux DevTools | Custom DevTools | None built-in |
| SSR | Yes | Yes (with Provider) | Yes |
| Middleware | Yes (persist, immer, etc.) | Extensions | Plugins |
| Learning curve | Very low | Low | Very low |
| Community | Largest | Growing | Smaller |
Zustand: The Store-Based Approach
Zustand uses a simple store pattern. Define a store with state and actions, use it in components.
import { create } from 'zustand'
interface TaskStore {
tasks: Task[]
addTask: (task: Task) => void
removeTask: (id: string) => void
}
const useTaskStore = create<TaskStore>((set) => ({
tasks: [],
addTask: (task) => set((state) => ({ tasks: [...state.tasks, task] })),
removeTask: (id) => set((state) => ({
tasks: state.tasks.filter(t => t.id !== id)
})),
}))
// In component
function TaskList() {
const tasks = useTaskStore((state) => state.tasks)
const addTask = useTaskStore((state) => state.addTask)
// ...
}
Strengths
- Simplest API. Create a store, use it. No providers, no context, no boilerplate.
- Selector-based re-renders. Components only re-render when the selected state changes.
- Middleware ecosystem. persist (localStorage), immer (immutable updates), devtools (Redux DevTools).
- Works outside React. Access store state in vanilla JS, utility functions, or API routes.
- Largest community. Most npm downloads, most tutorials, most Stack Overflow answers.
Weaknesses
- Store can grow unwieldy. Large stores become hard to manage (split into slices).
- No built-in atom composition. Derived state requires manual selectors or
subscribeWithSelector. - React-only hooks. Core API is framework-agnostic but the hooks are React-specific.
Best For
Most React applications. The default choice when you need state management beyond useState/useContext.
Jotai: The Atomic Approach
Jotai uses atoms — small, independent pieces of state that compose together. Inspired by Recoil but simpler.
import { atom, useAtom } from 'jotai'
const tasksAtom = atom<Task[]>([])
const taskCountAtom = atom((get) => get(tasksAtom).length)
const completedTasksAtom = atom((get) =>
get(tasksAtom).filter(t => t.completed)
)
// In component
function TaskList() {
const [tasks, setTasks] = useAtom(tasksAtom)
const [count] = useAtom(taskCountAtom) // auto-derived
// ...
}
Strengths
- Bottom-up composition. Build complex state from small, reusable atoms. Derived atoms auto-update.
- Minimal re-renders. Components subscribe to specific atoms — changing one atom doesn't re-render components using other atoms.
- Async atoms. Atoms can be async (data fetching) without additional libraries.
- Integrations. Official integrations with React Query, tRPC, Immer, and more.
- Code splitting friendly. Atoms are defined where they're used, not in a central store.
Weaknesses
- Provider needed for SSR. Server-side rendering requires wrapping your app in a Provider.
- Debugging harder. Atom-based state is more distributed — harder to see the full picture without DevTools.
- Conceptual overhead. The atom → derived atom → write atom pattern has a learning curve.
- Less intuitive for store-like patterns. If you think in "stores," Jotai's atom model may feel unnatural initially.
Best For
Applications with lots of independent state that composes together. Great for complex UIs with many interrelated but independently updating pieces.
Nanostores: The Framework-Agnostic Atoms
Nanostores provides tiny state atoms that work with any framework — React, Vue, Svelte, or vanilla JS.
import { atom, computed } from 'nanostores'
const $tasks = atom<Task[]>([])
const $taskCount = computed($tasks, tasks => tasks.length)
const $completedTasks = computed($tasks, tasks =>
tasks.filter(t => t.completed)
)
// React
import { useStore } from '@nanostores/react'
function TaskList() {
const tasks = useStore($tasks)
const count = useStore($taskCount)
// ...
}
// Vue (same atoms!)
import { useStore } from '@nanostores/vue'
// ... identical usage
Strengths
- Tiny. 0.3KB — the smallest state library. Your entire state management adds less than a single icon to your bundle.
- Framework-agnostic. Same atoms work in React, Vue, Svelte, Solid, and vanilla JS. Share state across micro-frontends or framework migrations.
- Simple. atom, map, computed — three concepts cover everything.
- Perfect for Astro. Astro uses multiple frameworks on one page. Nanostores is the recommended state solution.
- Lifecycle built-in.
onMounthandles initialization (fetch data on first subscriber).
Weaknesses
- Smaller ecosystem. Fewer middleware, fewer tutorials, smaller community.
- No DevTools. No built-in debugging tools (though state is simple enough to console.log).
- Less middleware. No built-in persist, no immer integration, fewer extensions.
- Less React-optimized. Zustand and Jotai have more React-specific optimizations.
Best For
Astro projects (multi-framework islands), library authors (no framework dependency), and teams planning framework migrations.
Performance Comparison
| Metric | Zustand | Jotai | Nanostores |
|---|---|---|---|
| Bundle size (min+gzip) | 1.1KB | 2.4KB | 0.3KB |
| Re-render efficiency | Selector-based | Atom-based | Subscription-based |
| 1000-atom update | Fast | Fastest | Fast |
| Memory overhead | Low | Low | Lowest |
All three are fast enough for any practical application. The performance differences are negligible unless you have thousands of atoms updating simultaneously.
When to Use Each
Zustand When:
- You want the simplest possible state management
- You're coming from Redux and want something familiar but lighter
- You need middleware (persist, devtools)
- Your state is naturally store-shaped (user, settings, cart)
Jotai When:
- You have lots of independent, composable state
- You want derived state that auto-updates
- You need async atoms (built-in data fetching)
- You like the Recoil mental model but want something simpler
Nanostores When:
- You're using Astro or multiple frameworks
- Bundle size is critical
- You want framework-agnostic state
- You're building a library or design system
FAQ
Can I use Zustand and Jotai together?
Yes, and some teams do — Zustand for "global" stores (user, settings) and Jotai for "local" composable state (UI state, form state). But pick one if you can.
What about Redux Toolkit?
Redux Toolkit is fine for large teams with established Redux patterns. But for new projects, Zustand provides a similar mental model with 90% less boilerplate.
What about Valtio?
Valtio uses mutable proxies (like MobX) — const state = proxy({ count: 0 }) then mutate directly. Great DX but the proxy model can be surprising. Consider it if you prefer mutable state patterns.
Do I even need a state library?
For most apps, useState + useContext + React Query covers 90% of state needs. Add a state library when context re-rendering becomes a problem or when you need state outside of React.
The Verdict
- Zustand for most React apps. Simplest API, largest community, battle-tested. The default choice.
- Jotai for complex, composable state. Best when you have many independent pieces of state that derive from each other.
- Nanostores for multi-framework projects and when bundle size matters. The right choice for Astro.
If unsure, start with Zustand. You'll know if you need Jotai's atom composition when you start writing complex selectors.