For most Next.js 16 App Router projects in 2026, pick Zustand for client state. Pick Redux Toolkit when a large team needs strict event logs, DevTools time travel, and RTK Query on the client. Pick Jotai when your state is a graph of small values that derive from each other, like editors, filters, and form builders. All three work with the App Router if you follow one rule: never keep a store in a module-level variable on the server.
That rule matters more than the library choice. Most state bugs I fix in Next.js codebases come from a global store that leaks one user's data into another user's server render, not from picking the "wrong" tool.
What changed since October 2026
All three libraries shipped releases in the past month, so older comparisons are out of date.
- Zustand 5.1.0 (November 6, 2026) made
persist.rehydrate()return a native Promise you can await, and taught thedevtoolsmiddleware to dispatch store actions from Redux DevTools through a newactionCreatorsoption. - Jotai 3.0 (September 8, 2026) landed as a mostly backward-compatible major that drops Node 20 and older TypeScript versions. Version 3.0.2 followed on November 9.
- Redux Toolkit 2.14 (November 3, 2026) sped up the dispatch path and cut RTK Query
Apitype instantiations by 22% on a 50-endpoint test app. Two days earlier, Mark Erikson published the RTK 3.0 roadmap: ESM-only packages,respondToreplacingextraReducers, and a target of roughly six to eight months out.
Adoption numbers from the week ending November 7, 2026: Zustand had about 91.8 million npm downloads, @reduxjs/toolkit about 48.3 million, and Jotai about 9.7 million. Downloads include CI and transitive installs, so treat them as a signal of ecosystem weight, not head count.
First, shrink the problem
Before you compare APIs, move state out of the client store where you can. In an App Router app, a lot of what used to live in Redux now has a better home:
- Server data belongs in Server Components or a cache like TanStack Query. I covered that split in Server Components did not replace TanStack Query.
- Filters, tabs, and pagination belong in the URL through
searchParams, so links and the back button work. - Form state belongs in the form, validated with a schema. See Zod vs Valibot for Next.js forms.
What remains tends to be small: a cart, a theme, an open sidebar, a multistep wizard, an editor canvas. The Redux docs say the same thing for the App Router: only put globally shared, mutable data in the store.
Zustand: the default for most apps
Zustand wins on size and on how little ceremony it adds. A store is a hook. You do not need a provider for a client-only SPA, and selectors keep re-renders narrow.
In Next.js you do need a provider, because a module-level store lives across requests on the server. The pattern is a vanilla store created per request and passed through context:
1'use client'2import { createContext, useContext, useState, type ReactNode } from 'react'3import { createStore, useStore } from 'zustand'45type CartState = { items: string[]; add: (id: string) => void }67const createCartStore = (initial: string[] = []) =>8 createStore<CartState>()((set) => ({9 items: initial,10 add: (id) => set((s) => ({ items: [...s.items, id] })),11 }))1213const CartContext = createContext<ReturnType<typeof createCartStore> | null>(null)1415export function CartProvider({ initial, children }: { initial?: string[]; children: ReactNode }) {16 const [store] = useState(() => createCartStore(initial))17 return <CartContext.Provider value={store}>{children}</CartContext.Provider>18}1920export function useCart<T>(selector: (s: CartState) => T) {21 const store = useContext(CartContext)22 if (!store) throw new Error('useCart must be used inside CartProvider')23 return useStore(store, selector)24}
The tradeoff: Zustand gives you no opinion on structure. On a team of ten, three developers will write stores three different ways unless you agree on a pattern. If you use persist, the 5.1 change helps: await rehydrate() before you render anything that depends on stored values, and the hydration mismatch warnings go away.
Redux Toolkit: pay for structure when you need it
Redux Toolkit costs more boilerplate than Zustand, and you get something back for it. Every change is a named action. You can replay a bug report from a DevTools export. Middleware and listeners give you one place to log, audit, or sync. RTK Query adds caching, polling, and tag-based invalidation for client-side fetching.
The official App Router guide uses the same per-request idea as the Zustand example above:
1import { configureStore } from '@reduxjs/toolkit'2import { cartSlice } from './features/cart/cartSlice'34export const makeStore = () =>5 configureStore({ reducer: { cart: cartSlice.reducer } })67export type AppStore = ReturnType<typeof makeStore>8export type RootState = ReturnType<AppStore['getState']>9export type AppDispatch = AppStore['dispatch']
A client StoreProvider then calls useState(makeStore) once and wraps the tree in the React-Redux Provider. Server Components must not read or write the store.
Choose Redux Toolkit when the app already runs on it, when compliance or support teams need an action log, or when many developers touch the same state. Do not start a new small product on it out of habit. With 3.0 planned as ESM-only and extraReducers on the way out, new code should already lean on the newer slice APIs.
Jotai: atoms for derived, fine-grained state
Jotai models state as atoms. A component subscribes to exactly the atoms it reads, and derived atoms recompute only when their inputs change. That fits UIs where dozens of small values depend on each other: a dashboard builder, a design tool, a pricing calculator with many inputs.
1import { atom } from 'jotai'23export const seatsAtom = atom(5)4export const pricePerSeatAtom = atom(12)5export const totalAtom = atom((get) => get(seatsAtom) * get(pricePerSeatAtom))
In the App Router, wrap the client tree in Jotai's Provider so each request gets its own store, and seed server values with useHydrateAtoms inside a client component.
The cost is debugging at scale. A graph of two hundred atoms is harder to reason about than one store with named actions. Jotai 3.0 also drops Node 20 support, so check your CI image before you upgrade.
How I decide on client projects
- Remove server data, URL state, and form state from the list first.
- If what remains fits on one screen of types, use Zustand with a per-request provider.
- If the app already uses Redux, or needs an audit trail of actions, stay on Redux Toolkit 2.x and follow the 3.0 roadmap.
- If the state is mostly derived values with fine-grained updates, use Jotai.
Whatever you pick, add one test that renders two requests with different users and checks that the second render shows nothing from the first. That test catches the shared-store bug no matter which library you chose. If you also plan to turn on the React Compiler, read React Compiler on Next.js 16 first, since it changes how much manual memoization your selectors need.
FAQ
Is Redux dead in 2026? No. Redux Toolkit 2.14 shipped on November 3, 2026, and a 3.0 roadmap is public. New small apps no longer need it by default.
Can I use Zustand without a provider in Next.js? Only for state that never renders on the server. For anything rendered during SSR, create the store per request.
Does Jotai work with Server Components? Server Components cannot read atoms. Read atoms in client components and pass server data in as props or through useHydrateAtoms.