Design wechseln

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

Easton editorial illustration: build pipeline conveyor

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.

Zustand: 1KB
Paketgröße
gzip-Größe, Zustand ist leichter
Zustand: 10 Min.
Lernkurve
Einstiegszeit, Zustand ist einfacher
Zustand: manuell
Performance
manueller Selector vs. automatische feingranulare Updates
Zustand: 80%
Einsatzszenario
die meisten Projekte wählen Zustand, komplexe Szenarien Jotai

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:

  1. Provider in template.tsx statt layout.tsx (bei jedem Routenwechsel neu)
  2. 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)

  1. Server Components ohne State-Management

    • Keine Hooks, kein useStore oder useAtom
    • Interaktive Teile als 'use client'
  2. Provider-Position zählt

    • Je tiefer, desto besser für statische Optimierung
    • Aber erreichbar für alle state-abhängigen Komponenten
  3. 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

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 …

  1. Einfaches persönliches Projekt / kleine App
    → Zuerst Context API – reicht das, nicht wechseln
    → Bei Performance-Problemen: Zustand

  2. Mittlere SaaS-App / E-Commerce
    → Direkt Zustand
    → Einfach, stabil, teamfreundlich

  3. Komplexes Dashboard / Echtzeit-App
    → Jotai in Betracht ziehen
    → Komplexe Abhängigkeiten, klarerer Code

  4. Großes Team / strenge Vorgaben
    → Redux Toolkit kann passender sein
    → Mehr Struktur und etablierte Patterns

  5. State-Updates außerhalb von React
    → Zustand
    → Bei Jotai möglich, aber unnatürlich

  6. Intensiver Suspense-Einsatz
    → Jotai
    → Asynchrone Atoms nativ unterstützt

Progressive Strategie

Ich empfehle schrittweise:

  1. Phase 1: Context API

    • Projektstart, wenig State – Context reicht
    • Nicht zu früh optimieren
  2. Phase 2: Zustand

    • Context wird langsam oder State komplexer
    • Zustand löst ~80 % der Fälle
  3. 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?
Redux:
• 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?
Zustand wählen, wenn:
• 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?
Das Design von Context führt dazu, dass bei jeder Aktualisierung des Provider-Werts alle Komponenten neu rendern, die diesen Context konsumieren – selbst wenn sich nur ein Feld im Objekt ändert.

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?
Beim Einsatz von Zustand im Next.js App Router muss ein globaler Store vermieden werden, der zu Datenlecks auf dem Server führt. Empfohlen wird das Store-Factory-Muster:

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?
Jotai erfordert im Next.js App Router einen eigenen Provider pro Request:

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?
Nein. Server Components werden serverseitig gerendert und haben keinen Client-State – weder State-Management-Bibliotheken noch React Hooks sind dort nutzbar.

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

Kommentare

Melde dich mit GitHub an, um einen Kommentar zu hinterlassen

Easton BlogEaston Blog