Next.js State-Management: Zustand vs Jotai – Praxisvergleich und Auswahlhilfe

Die Fehlermeldung ist wieder da – zum 17. Mal den Redux-Action-Creator angepasst. Das Projekt hat nur eine Warenkorb-Funktion, und schon liegen drei Konfigurationsdateien vor.
Warum muss das so kompliziert sein? Für ein kleines Projekt fühlt sich Redux an wie Kanonen auf Spatzen; wechselt man zu Context API, rendert ein State-Update plötzlich die halbe Seite neu. Das rote Flackern im Performance-Panel macht einem Kopfschmerzen.
Deshalb sind Zustand und Jotai in den letzten Jahren so populär geworden – sie versprechen „leichtgewichtig“ und „hohe Performance“. Aber welches soll man wählen? In diesem Artikel geht es um:
- Warum Redux und Context in der Praxis Probleme bereiten (echte Fallstricke)
- Was Zustand und Jotai grundlegend unterscheidet
- Welches Tool in welchem Szenario passt (Entscheidungsbaum)
- Wie man beides im Next.js App Router einsetzt (Erfahrungen aus der Praxis)
Warum nicht Redux und Context?
Wie „schwer“ ist Redux wirklich?
Redux ist nicht schlecht – für viele Projekte ist es einfach zu viel.
Sie schreiben Action Types, Action Creators, Reducers und konfigurieren den Store. Für eine simple „In den Warenkorb legen“-Funktion berühren Sie leicht drei oder vier Dateien. Das Boilerplate nervt auf Dauer.
Noch wichtiger: Neue Teammitglieder steigen steil ein. Was ist dispatch? Warum pure functions? Wozu Middleware? Diese Konzepte brauchen Zeit.
Für eine To-do-Liste oder einen persönlichen Blog ist Redux wie mit dem Panzer einkaufen gehen – Sie kommen an, aber es lohnt sich nicht.
Die Performance-Falle der Context API
Context ist einfach – offiziell eingebaut, keine extra Bibliothek.
Aber: die Performance.
Sobald sich der Provider-Wert ändert, rendern alle konsumierenden Komponenten neu – auch wenn Sie nur ein Feld nutzen.
Ich habe einmal ein Formular mit Context für Feld-States gebaut. Ein onChange in einem Input löste 20 Re-Renders aus. Die Flame Chart in den Chrome DevTools war schmerzhaft anzusehen.
Mit memo, useMemo und aufgeteilten Contexts lässt sich optimieren – aber der optimierte Code ist oft nicht einfacher als Redux.
Der Reiz leichtgewichtiger Alternativen
Deshalb sind Zustand und Jotai so beliebt.
Ihr Versprechen ist klar:
- Einfache API, schneller Einstieg (Zustand in 10 Minuten)
- Performance-Optimierung eingebaut, kein manuelles Tuning nötig
- Kleine Bundle-Größe (Zustand nur 1 KB, gzip noch kleiner)
Die Zahlen sprechen dafür: Laut Statistiken aus 2025 stieg die Nutzung von Zustand im vergangenen Jahr um 150 %. Immer mehr Entwickler wechseln von Redux zu diesen leichtgewichtigen Lösungen.
Die neue Frage: Zustand oder Jotai?
Zustand vs Jotai – die Kernunterschiede
Beide wirken oberflächlich wie „leichtgewichtiges State-Management“, aber das Design ist grundverschieden.
State-Modell: Ein Store vs. atomare Stückchen
In der offiziellen Dokumentation steht es treffend: „Zustand is like Redux. Jotai is like Recoil.“
Zustand ist im Wesentlichen ein vereinfachtes Redux. Ein Store, alles drin – wie ein Einkaufszentrum mit zentraler Verwaltung.
// Zustand: ein großer Store
const useStore = create((set) => ({
user: null,
cart: [],
theme: 'light',
// alle States hier
}))
Jotai ist atomisiert. Jeder State ist ein eigenes Atom – wie viele kleine Stände, die unabhängig arbeiten.
// Jotai: unabhängige Atoms
const userAtom = atom(null)
const cartAtom = atom([])
const themeAtom = atom('light')
Dieser Unterschied bestimmt die passenden Einsatzszenarien.
Speicherort: außerhalb vs. innerhalb des Komponentenbaums
Zustands-Stores leben auf Modulebene, außerhalb von React. Importieren, aktualisieren – ohne Provider.
Jotai-Atoms hängen am Komponentenbaum und brauchen Context. Ein Provider in der Root-Komponente ist Pflicht.
Was bedeutet das?
State außerhalb von React aktualisieren – z. B. in Utility-Funktionen oder WebSocket-Callbacks – ist mit Zustand deutlich einfacher. Bei Jotai geht es auch, aber umständlicher.
Performance: manuelle Optimierung vs. automatisch optimal
Jotai abonniert standardmäßig feingranular: Komponenten reagieren nur auf ihre Atoms.
Zustand braucht Selektoren:
// Nicht empfohlen: ganzen Store abonnieren
const store = useStore()
// Empfohlen: Selector für den benötigten Teil
const user = useStore(state => state.user)
Selektoren bei Zustand sind aber unkompliziert – man muss nur daran denken, sonst drohen Fallstricke.
Kurzfassung
- Zustand: ein Store, außerhalb von React, manuelle Selektoren
- Jotai: atomisierte Atoms, innerhalb von React, Performance automatisch optimiert
Die Wahl hängt von Ihrem Projekt ab.
Wann Zustand wählen?
Wenn Ihr Projekt diese Merkmale hat, ist Zustand meist die beste Wahl.
Mittlere Apps ohne unnötige Komplexität
Die meisten Projekte brauchen kein komplexes State-Management.
Ein E-Commerce-Shop verwaltet global typischerweise: Benutzer, Warenkorb, Theme. Dafür passt Zustand hervorragend.
Die API ist so einfach, dass man kaum lernen muss. Ein vollständiges Beispiel:
// store.js
import create from 'zustand'
const useStore = create((set) => ({
cart: [],
addToCart: (item) => set((state) => ({
cart: [...state.cart, item]
})),
removeFromCart: (id) => set((state) => ({
cart: state.cart.filter(item => item.id !== id)
})),
}))
// CartButton.jsx
function CartButton() {
const addToCart = useStore(state => state.addToCart)
return <button onClick={() => addToCart(item)}>In den Warenkorb</button>
}
// CartCount.jsx
function CartCount() {
const count = useStore(state => state.cart.length)
return <span>{count}</span>
}
Kein Provider, keine Action Types, kein Reducer. State und Methoden definieren, direkt nutzen.
Neue Teammitglieder sind in 10 Minuten produktiv.
State außerhalb von React aktualisieren
Ein Alleinstellungsmerkmal von Zustand.
WebSocket-Nachrichten eintreffen und State muss sich ändern:
// websocket.js
import { useStore } from './store'
socket.on('message', (data) => {
// Store-Methode direkt aufrufen, ohne Komponente
useStore.getState().updateMessages(data)
})
Oder in einer Utility-Funktion anhand des aktuellen States entscheiden:
// utils.js
import { useStore } from './store'
export function checkPermission() {
const user = useStore.getState().user
return user?.role === 'admin'
}
Bei Jotai ist das unbequemer – Atoms sind an den Komponentenbaum gebunden.
Next.js-SSR-freundlich
Zustand ist in Next.js besonders gut unterstützt.
Die offizielle Dokumentation hat ein eigenes Kapitel zur Next.js-Integration mit App-Router-Best-Practices. Community-Erfahrungen sind reichlich – Probleme finden meist schnell eine Lösung.
Mit Next.js 13+ App Router ist Zustand eine der stabilsten Optionen. Die Konfiguration folgt weiter unten im Detail.
Wann Zustand nicht optimal ist
Wenn States komplexe abgeleitete Beziehungen haben.
Beispiel: Filter mit 10 Bedingungen, deren Optionen voneinander abhängen. Dann wird Zustand-Code schnell unübersichtlich – hier glänzt Jotai.
Wann Jotai wählen?
Atomisiertes Design ist in manchen Szenarien unschlagbar.
Komplexe State-Abhängigkeiten
Jotais Stärke.
Produktfilter mit Marke, Preisbereich, Bewertung – verfügbare Marken hängen vom Preisbereich ab, die Produktliste von allen Filtern.
Mit Zustand verwalten Sie Abhängigkeiten manuell – leicht chaotisch.
Mit Jotai bleibt es klar:
// Basis-Atoms
const brandAtom = atom([])
const priceRangeAtom = atom([0, 1000])
const ratingAtom = atom(0)
// Abgeleitetes Atom: verfügbare Marken (abhängig vom Preisbereich)
const availableBrandsAtom = atom((get) => {
const priceRange = get(priceRangeAtom)
return fetchBrands(priceRange) // reagiert automatisch auf priceRange
})
// Abgeleitetes Atom: gefilterte Produkte (abhängig von allen Filtern)
const filteredProductsAtom = atom((get) => {
const brands = get(brandAtom)
const priceRange = get(priceRangeAtom)
const rating = get(ratingAtom)
return products.filter(/* Filterlogik */)
})
Jedes Atom kümmert sich nur um seine Abhängigkeiten – Jotai verfolgt sie automatisch.
In Komponenten:
function FilterPanel() {
const [brands, setBrands] = useAtom(brandAtom)
const availableBrands = useAtomValue(availableBrandsAtom)
// brands ändert sich → availableBrands wird neu berechnet
}
In solchen Szenarien ist Jotai deutlich klarer als Zustand.
Extreme Performance-Anforderungen
Atomare Subscriptions performen wirklich gut.
Ein Echtzeit-Dashboard mit 50 Komponenten und unterschiedlichen Metriken: Context oder unoptimiertes Zustand kann bei einem Update viele Re-Renders auslösen.
Jotai nicht – jede Komponente abonniert nur ihr Atom.
Die Dokumentation formuliert es klar: „This is the most performant by default.“ Nur das geänderte Atom triggert ein Re-Render.
Code-Splitting in großen Apps
Jotai-Atoms lassen sich lazy laden.
Atoms in verschiedenen Dateien, Import erst bei Bedarf – hilfreich für die First-Paint-Performance großer Apps.
Zustand-Stores sind meist monolithisch; Aufteilung geht, aber weniger natürlich als bei Jotai.
Projekte mit intensivem Suspense-Einsatz
Bei viel React Suspense für asynchrone Daten hat Jotai native Unterstützung.
Asynchrone Atoms sind intuitiv:
const userAtom = atom(async () => {
const res = await fetch('/api/user')
return res.json()
})
function UserProfile() {
const user = useAtomValue(userAtom)
// automatisches Suspense bis Daten da sind
return <div>{user.name}</div>
}
Zustand funktioniert mit Suspense, braucht aber extra Wrapper – weniger elegant.
Lernkurve bei Jotai
Jotai ist etwas konzeptueller: Lesen/Schreiben von Atoms, abgeleitete und asynchrone Atoms brauchen Einarbeitung. Neue Teammitglieder starten langsamer als mit Zustand.
Die Dokumentation ist ehrlich gesagt weniger einsteigerfreundlich als bei Zustand – manche APIs will man zweimal lesen.
Next.js App Router – Best Practices
Genug Theorie – praktische Hinweise für Next.js 13+ App Router.
Zustand richtig einsetzen
Große Falle: keinen globalen Store verwenden
Viele (ich am Anfang auch) schreiben so:
// ❌ Falsch: globaler Store
import create from 'zustand'
const useStore = create((set) => ({
user: null,
setUser: (user) => set({ user })
}))
Client-seitig ok – in Next.js SSR teilt sich der Store zwischen Requests. User A sieht evtl. Daten von User B – Sicherheitsrisiko.
Empfohlen: Store-Factory-Muster:
// lib/store.js
import { createStore } from 'zustand/vanilla'
export function createUserStore(initialState) {
return createStore((set) => ({
user: initialState?.user || null,
setUser: (user) => set({ user })
}))
}
Provider in Client Components:
// components/StoreProvider.jsx
'use client'
import { createContext, useContext, useRef } from 'react'
import { useStore } from 'zustand'
import { createUserStore } from '@/lib/store'
const StoreContext = createContext(null)
export function StoreProvider({ children, initialState }) {
const storeRef = useRef()
if (!storeRef.current) {
storeRef.current = createUserStore(initialState)
}
return (
<StoreContext.Provider value={storeRef.current}>
{children}
</StoreContext.Provider>
)
}
export function useUserStore(selector) {
const store = useContext(StoreContext)
return useStore(store, selector)
}
Im Root-Layout:
// app/layout.jsx
import { StoreProvider } from '@/components/StoreProvider'
export default function RootLayout({ children }) {
// Daten können hier vorab geladen werden
const initialState = { user: null }
return (
<html>
<body>
<StoreProvider initialState={initialState}>
{children}
</StoreProvider>
</body>
</html>
)
}
Jeder Request hat einen isolierten Store – keine Datenvermischung.
Hinweise:
- Server Components dürfen den Store nicht direkt lesen/schreiben
- Daten-Fetching serverseitig, Initialwerte per initialState an den Client
- Kein blockierendes Daten-Fetching im Root-Layout – schadet der Performance
Jotai und SSR-Hydration
Die größte Falle bei Jotai: Hydration-Fehler.
Eigener Provider pro Request:
// app/providers.jsx
'use client'
import { Provider } from 'jotai'
export function Providers({ children }) {
return <Provider>{children}</Provider>
}
// app/layout.jsx
import { Providers } from './providers'
export default function RootLayout({ children }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}
Server-Daten einbinden:
Mit useHydrateAtoms Server-Daten in Atoms injizieren:
'use client'
import { useHydrateAtoms } from 'jotai/utils'
import { userAtom } from '@/atoms'
export function HydrateAtoms({ initialUser, children }) {
useHydrateAtoms([[userAtom, initialUser]])
return children
}
Achtung: useHydrateAtoms wirkt nur beim ersten Render. Bei router.push im App Router wird dasselbe Atom beim zweiten Besuch nicht erneut hydriert.
Lösungen:
- Provider in
template.tsxstattlayout.tsx(bei jedem Routenwechsel neu) - Seitenbezogener Provider statt global
Hydration-Fehler mit atomWithStorage:
Bei atomWithStorage für Formulardaten:
- Server: leeres Formular
- Client: Wert aus localStorage
- Ergebnis: React Hydration Mismatch
Lösung: Formular in useEffect füllen oder useHydrateAtoms + useSyncExternalStore.
Allgemeine Prinzipien (beide Bibliotheken)
-
Server Components ohne State-Management
- Keine Hooks, kein
useStoreoderuseAtom - Interaktive Teile als
'use client'
- Keine Hooks, kein
-
Provider-Position zählt
- Je tiefer, desto besser für statische Optimierung
- Aber erreichbar für alle state-abhängigen Komponenten
-
Root-Layout nicht blockieren
- Kein
await fetch()für User-Daten im Root-Layout - Das untergräbt Streaming und Server Components
- Dedizierte Client-Komponente für Fetch und Initialisierung
- Kein
Diese Fallen kenne ich aus eigener Erfahrung – die Prinzipien sparen viel Debug-Zeit.
Meine Auswahl-Empfehlung
Trotz allem die Frage: Welches Tool?
Es gibt keine Standardantwort – aber einen Entscheidungsbaum.
Schneller Entscheidungsbaum
Wenn Ihr Projekt …
-
Einfaches persönliches Projekt / kleine App
→ Zuerst Context API – reicht das, nicht wechseln
→ Bei Performance-Problemen: Zustand -
Mittlere SaaS-App / E-Commerce
→ Direkt Zustand
→ Einfach, stabil, teamfreundlich -
Komplexes Dashboard / Echtzeit-App
→ Jotai in Betracht ziehen
→ Komplexe Abhängigkeiten, klarerer Code -
Großes Team / strenge Vorgaben
→ Redux Toolkit kann passender sein
→ Mehr Struktur und etablierte Patterns -
State-Updates außerhalb von React
→ Zustand
→ Bei Jotai möglich, aber unnatürlich -
Intensiver Suspense-Einsatz
→ Jotai
→ Asynchrone Atoms nativ unterstützt
Progressive Strategie
Ich empfehle schrittweise:
-
Phase 1: Context API
- Projektstart, wenig State – Context reicht
- Nicht zu früh optimieren
-
Phase 2: Zustand
- Context wird langsam oder State komplexer
- Zustand löst ~80 % der Fälle
-
Phase 3: Jotai oder bei Zustand bleiben
- Sehr komplexe Abhängigkeiten → Jotai
- Sonst bei Zustand bleiben
- Nicht der Technik wegen wechseln
Kombinierbar?
Ja!
Zustand und Jotai schließen sich nicht aus. Projekte nutzen oft:
- Zustand für globale Konfiguration (User, Theme)
- Jotai für komplexe Formular-States
Das passende Werkzeug für das konkrete Problem – völlig in Ordnung.
Eigene Praxis
Meine Wahl:
- Persönlicher Blog: kein State-Management, Server Components + URL-State reichen
- Admin-Backend: Zustand plus React Query für Server-State
- Echtzeit-Dashboard: Jotai – Abhängigkeiten zu komplex für übersichtliches Zustand
Nicht gleich in Technologie-Debatten verfallen. Einfachste Lösung zuerst, bei Bedarf upgraden.
Viele Projekte kommen mit Context API aus – unterschätzen Sie das nicht.
Fazit
Zurück zur Eingangsfrage.
Redux zu schwer? Für viele Projekte ja – Kanonen auf Spatzen.
Context zu langsam? Unoptimiert wird Re-Rendering zum Engpass.
Zustand oder Jotai? Nach Szenario:
- Meist reicht Zustand – einfach und stabil
- Komplexe Abhängigkeiten: Jotai eleganter
- Unsicher? Zustand testen, bei Bedarf wechseln
Next.js App Router:
- Kein globaler Store
- Provider pro Request
- Server Components ohne State
Keine Technologie hat die eine richtige Antwort. Lassen Sie sich nicht blind von Vergleichsartikeln leiten – auch nicht von diesem. Wichtig ist eine Lösung, mit der Sie und Ihr Team produktiv sind.
Wenn Sie zögern: Demo bauen, ausprobieren. Zehn Minuten Praxis schlagen zehn Artikel.
Viel Erfolg bei der Wahl.
FAQ
Was sind die Unterschiede zwischen Redux, Context, Zustand und Jotai?
• Vorteile: Leistungsstark, reiches Ökosystem, geeignet für sehr große Projekte
• Nachteile: Zu schwergewichtig, viel Boilerplate, hohe Lernkurve
• Einsatz: Sehr große Projekte mit komplexem State-Management
Context API:
• Vorteile: In React eingebaut, keine zusätzliche Bibliothek nötig
• Nachteile: Schlechte Performance – ein State-Update kann den gesamten Subtree neu rendern
• Einsatz: Einfacher globaler State mit seltenen Updates
Zustand:
• Vorteile: Einfach und direkt, wenig Code, niedrige Lernkurve
• Nachteile: Nicht ideal für sehr große Projekte
• Einsatz: Die meisten Projekte, insbesondere kleine und mittlere
Jotai:
• Vorteile: Atomisierter State, feingranulare Updates, gute Performance
• Nachteile: Höhere Lernkurve, komplexerer Code
• Einsatz: Große Projekte mit extremen Performance-Anforderungen
Empfehlung: Für die meisten Projekte Zustand wählen; für große Projekte oder maximale Performance Jotai.
Wann Zustand und wann Jotai?
• Die meisten Projekte
• Kleine bis mittlere Projekte
• Einfaches, direktes State-Management gewünscht ist
• Das Team schnell produktiv werden soll
• Wenig Code im Vordergrund steht
Jotai wählen, wenn:
• Große Projekte
• Maximale Performance gefordert ist
• Häufige State-Updates anstehen
• Feingranulare Updates nötig sind
• Das Team bereits Erfahrung hat
Entscheidungsbaum:
• Kleines Projekt → Zustand
• Großes Projekt → Jotai
• Hohe Performance-Anforderungen → Jotai
• Einfachheit im Vordergrund → Zustand
Empfehlung: Für die meisten Projekte Zustand; Jotai nur bei großen Projekten oder extremen Performance-Anforderungen.
Warum ist die Context API langsam?
Lösungsansätze:
• Context aufteilen (verschiedene Contexts nach Funktion trennen)
• Optimierung mit useMemo und memo
• Wechsel zu Zustand oder Jotai (beide unterstützen feingranulare Subscriptions)
Empfehlung: Bei häufigen State-Updates Context API vermeiden und stattdessen Zustand oder Jotai einsetzen.
Wie nutzt man Zustand im Next.js App Router?
1. Mit createStore eine Store-Factory-Funktion erstellen
2. In Client Components eine Store-Instanz mit useRef anlegen
3. Die Instanz über einen Context Provider an Kindkomponenten weitergeben
Wichtige Punkte:
• Komponenten, die den Store nutzen, müssen als 'use client' markiert sein
• Server Components können kein State-Management verwenden
• Jeder Request sollte eine eigene Store-Instanz haben, um Datenvermischung zu vermeiden
So bleibt der State pro Request isoliert, bei gleichzeitiger Einfachheit von Zustand.
Wie nutzt man Jotai im Next.js App Router?
1. Provider-Komponente im Root-Layout oder in template.jsx einbinden
2. useHydrateAtoms nutzen, um Server-Daten als Initialwerte zu injizieren
3. Hinweis: useHydrateAtoms wirkt nur beim ersten Render
Wichtige Punkte:
• Komponenten, die Atoms nutzen, müssen Client Components sein
• Server Components können kein State-Management verwenden
• Atomisiertes Design ermöglicht automatisch feingranulare Updates
Vorteile:
• Nur Komponenten, die ein bestimmtes Atom abonnieren, werden aktualisiert
• Native Unterstützung für Suspense und asynchrone Daten
• Geeignet für komplexe State-Abhängigkeiten und große Projekte
Hinweis: Jotai hat eine höhere Lernkurve als Zustand, überzeugt aber in komplexen Szenarien durch Performance und Code-Klarheit.
Können Server Components State-Management nutzen?
Der richtige Ansatz:
• Server Components übernehmen das Daten-Fetching (fetch, Datenbankabfragen usw.)
• Daten per Props an Client Components weitergeben
• Client Components verwalten State und Benutzerinteraktionen
Architekturmuster:
Server Component holt Initialdaten → übergibt per Props → Client Component verwaltet State und Interaktion mit Zustand/Jotai
Diese Aufteilung lässt Server Components sich auf Daten und SEO konzentrieren, Client Components auf Interaktion und State – und nutzt die Stärken des Next.js App Routers voll aus.
11 Min. Lesezeit · Veröffentlicht am: 19. Dez. 2025 · Aktualisiert am: 14. Juli 2026
Next.js Komplettleitfaden
Wenn du über die Suche hier gelandet bist, kommst du am schnellsten weiter, indem du zum vorherigen oder nächsten Beitrag dieser Serie springst.
Vorheriger
Next.js + Prisma Komplett-Guide: Von der Konfiguration zur Praxis (inkl. Lösung für Connection-Leaks)
Vollständiges Next.js + Prisma-Tutorial mit Umgebungskonfiguration, Schema-Design, CRUD-Praxis und Lösung für Connection-Leaks beim Hot Reload – damit Sie Prisma ORM schnell produktiv nutzen.
Teil 23 von 51
Nächster
Next.js Caching vollständig verstehen: Wann revalidate richtig einsetzen
Die vier Cache-Ebenen von Next.js im Detail, revalidate, revalidatePath und revalidateTag gezielt nutzen, typische Probleme bei nicht aktualisierten Daten lösen – mit vollständiger Fehlersuche und Best Practices
Teil 25 von 51
Ähnliche Beiträge
Next.js App Router Einstieg: Kernkonzepte und Grundlagen im Überblick

Next.js App Router Einstieg: Kernkonzepte und Grundlagen im Überblick
Next.js 15 in der Praxis: Wie ich an einem Wochenende ein produktionsreifes Blog-System aufgebaut habe


Kommentare
Melde dich mit GitHub an, um einen Kommentar zu hinterlassen