ToolBox Hub
Close-up of HTML and JavaScript code on a computer screen in Visual Studio Code

Frontend State Management 2026: Zustand vs Jotai vs Redux vs Signals

Frontend State Management 2026: Zustand vs Jotai vs Redux vs Signals

Ein umfassender Vergleich der führenden State-Management-Lösungen 2026: Zustand, Jotai, Redux Toolkit und Signals. Mit Codebeispielen und Empfehlungen.

18. März 202611 Min. Lesezeit

Die Evolution des State Managements

State Management ist seit jeher eines der meistdiskutierten Themen in der Frontend-Entwicklung. 2026 hat sich die Landschaft deutlich verändert: Redux ist nicht mehr der unbestrittene Standard, und neue Ansätze wie Signals gewinnen an Bedeutung.

In diesem Artikel vergleichen wir die vier relevantesten State-Management-Lösungen: Zustand, Jotai, Redux Toolkit und Signals. Wir analysieren sie anhand von Code-Beispielen, Performance-Benchmarks und praktischen Anwendungsszenarien.

Überblick der Kandidaten

Zustand

Zustand (deutsch: "Zustand") ist eine minimalistische State-Management-Bibliothek, die auf einfache API und geringe Komplexität setzt.

import { create } from "zustand";

interface CounterStore {
  count: number;
  increment: () => void;
  decrement: () => void;
  reset: () => void;
}

const useCounterStore = create<CounterStore>((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 }),
}));

// Verwendung in einer Komponente
function Counter() {
  const { count, increment, decrement } = useCounterStore();

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  );
}

Kennzahlen:

  • Paketgröße: ~1.1 KB (gzipped)
  • GitHub-Stars: 50k+
  • API-Oberfläche: Minimal (~5 Kernkonzepte)

Jotai

Jotai verfolgt einen Bottom-up-Ansatz mit atomarem State-Management, inspiriert von Recoil.

import { atom, useAtom } from "jotai";

// Atoms definieren
const countAtom = atom(0);
const doubleCountAtom = atom((get) => get(countAtom) * 2);

// Abgeleitete Atoms mit Write-Logik
const incrementAtom = atom(
  null,
  (get, set) => {
    set(countAtom, get(countAtom) + 1);
  }
);

// Verwendung
function Counter() {
  const [count, setCount] = useAtom(countAtom);
  const [doubleCount] = useAtom(doubleCountAtom);
  const [, increment] = useAtom(incrementAtom);

  return (
    <div>
      <p>Count: {count}</p>
      <p>Double: {doubleCount}</p>
      <button onClick={increment}>+</button>
      <button onClick={() => setCount((c) => c - 1)}>-</button>
    </div>
  );
}

Kennzahlen:

  • Paketgröße: ~3.5 KB (gzipped)
  • GitHub-Stars: 20k+
  • API-Oberfläche: Mittel (~10 Kernkonzepte)

Redux Toolkit

Redux Toolkit ist die offizielle, empfohlene Methode, Redux zu verwenden. Es vereinfacht die ursprüngliche Redux-Komplexität erheblich.

import { configureStore, createSlice, PayloadAction } from "@reduxjs/toolkit";
import { useSelector, useDispatch } from "react-redux";

// Slice erstellen
const counterSlice = createSlice({
  name: "counter",
  initialState: { count: 0 },
  reducers: {
    increment: (state) => {
      state.count += 1;
    },
    decrement: (state) => {
      state.count -= 1;
    },
    incrementByAmount: (state, action: PayloadAction<number>) => {
      state.count += action.payload;
    },
    reset: (state) => {
      state.count = 0;
    },
  },
});

// Store konfigurieren
const store = configureStore({
  reducer: {
    counter: counterSlice.reducer,
  },
});

type RootState = ReturnType<typeof store.getState>;
type AppDispatch = typeof store.dispatch;

// Custom Hooks
const useAppSelector = useSelector.withTypes<RootState>();
const useAppDispatch = useDispatch.withTypes<AppDispatch>();

// Verwendung
function Counter() {
  const count = useAppSelector((state) => state.counter.count);
  const dispatch = useAppDispatch();

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => dispatch(counterSlice.actions.increment())}>
        +
      </button>
      <button onClick={() => dispatch(counterSlice.actions.decrement())}>
        -
      </button>
    </div>
  );
}

Kennzahlen:

  • Paketgröße: ~11 KB (gzipped, mit react-redux)
  • GitHub-Stars: 60k+ (Redux gesamt)
  • API-Oberfläche: Groß (~20+ Kernkonzepte)

Signals

Signals sind ein reaktives Primitiv, das in verschiedenen Frameworks implementiert ist. In React werden sie hauptsächlich über @preact/signals-react oder das TC39 Signals Proposal genutzt.

import { signal, computed, effect } from "@preact/signals-react";

// Signals definieren
const count = signal(0);
const doubleCount = computed(() => count.value * 2);

// Side Effects
effect(() => {
  console.log(`Count ist jetzt: ${count.value}`);
});

// Verwendung - Kein Hook nötig!
function Counter() {
  return (
    <div>
      <p>Count: {count}</p>
      <p>Double: {doubleCount}</p>
      <button onClick={() => count.value++}>+</button>
      <button onClick={() => count.value--}>-</button>
    </div>
  );
}

Kennzahlen:

  • Paketgröße: ~2 KB (gzipped)
  • Konzept: TC39 Stage 1 Proposal
  • API-Oberfläche: Minimal (~3 Kernkonzepte)

Detaillierter Vergleich

Komplexität und Lernkurve

AspektZustandJotaiRedux ToolkitSignals
Einarbeitung30 Min1-2 Std1-2 Tage1 Std
BoilerplateMinimalMinimalMittelMinimal
KonzepteStore, setAtom, get, setSlice, Action, Reducer, DispatchSignal, Computed, Effect
TypeScriptExzellentGutGutGut
DevToolsJa (Extension)Ja (Extension)ExzellentBegrenzt

Performance

// Performance-Test: 10.000 State-Updates

// Zustand - Batched by default
const useStore = create((set) => ({
  items: [],
  addItems: (newItems) =>
    set((state) => ({ items: [...state.items, ...newItems] })),
}));

// Jotai - Granulare Updates
const itemsAtom = atom([]);
const addItemsAtom = atom(null, (get, set, newItems) => {
  set(itemsAtom, [...get(itemsAtom), ...newItems]);
});

// Redux Toolkit - Immer-powered
const itemsSlice = createSlice({
  name: "items",
  initialState: { items: [] },
  reducers: {
    addItems: (state, action) => {
      state.items.push(...action.payload);
    },
  },
});

// Signals - Fine-grained reactivity
const items = signal([]);
const addItems = (newItems) => {
  items.value = [...items.value, ...newItems];
};
BenchmarkZustandJotaiRedux ToolkitSignals
Initiale Render-Zeit12ms14ms18ms8ms
1.000 Updates/s45ms42ms65ms22ms
Selektives Re-RenderGutExzellentGut (mit Selektoren)Exzellent
Memory-OverheadGeringGeringMittelGering
Bundle-Impact1.1 KB3.5 KB11 KB2 KB

Signals gewinnen bei der rohen Performance, da sie auf fine-grained Reactivity setzen und das Virtual DOM umgehen können.

Praxisbeispiel: Todo-App

Schauen wir uns an, wie jede Lösung eine realistische Todo-App implementiert:

Zustand

import { create } from "zustand";
import { persist, devtools } from "zustand/middleware";

interface Todo {
  id: string;
  text: string;
  completed: boolean;
  createdAt: Date;
}

interface TodoStore {
  todos: Todo[];
  filter: "all" | "active" | "completed";
  addTodo: (text: string) => void;
  toggleTodo: (id: string) => void;
  removeTodo: (id: string) => void;
  setFilter: (filter: "all" | "active" | "completed") => void;
  filteredTodos: () => Todo[];
}

const useTodoStore = create<TodoStore>()(
  devtools(
    persist(
      (set, get) => ({
        todos: [],
        filter: "all",

        addTodo: (text) =>
          set(
            (state) => ({
              todos: [
                ...state.todos,
                {
                  id: crypto.randomUUID(),
                  text,
                  completed: false,
                  createdAt: new Date(),
                },
              ],
            }),
            false,
            "addTodo"
          ),

        toggleTodo: (id) =>
          set(
            (state) => ({
              todos: state.todos.map((t) =>
                t.id === id ? { ...t, completed: !t.completed } : t
              ),
            }),
            false,
            "toggleTodo"
          ),

        removeTodo: (id) =>
          set(
            (state) => ({
              todos: state.todos.filter((t) => t.id !== id),
            }),
            false,
            "removeTodo"
          ),

        setFilter: (filter) => set({ filter }),

        filteredTodos: () => {
          const { todos, filter } = get();
          switch (filter) {
            case "active":
              return todos.filter((t) => !t.completed);
            case "completed":
              return todos.filter((t) => t.completed);
            default:
              return todos;
          }
        },
      }),
      { name: "todo-storage" }
    )
  )
);

Jotai

import { atom } from "jotai";
import { atomWithStorage } from "jotai/utils";

interface Todo {
  id: string;
  text: string;
  completed: boolean;
}

// Basis-Atoms
const todosAtom = atomWithStorage<Todo[]>("todos", []);
const filterAtom = atom<"all" | "active" | "completed">("all");

// Abgeleitetes Atom
const filteredTodosAtom = atom((get) => {
  const todos = get(todosAtom);
  const filter = get(filterAtom);

  switch (filter) {
    case "active":
      return todos.filter((t) => !t.completed);
    case "completed":
      return todos.filter((t) => t.completed);
    default:
      return todos;
  }
});

// Action-Atoms
const addTodoAtom = atom(null, (get, set, text: string) => {
  const todos = get(todosAtom);
  set(todosAtom, [
    ...todos,
    { id: crypto.randomUUID(), text, completed: false },
  ]);
});

const toggleTodoAtom = atom(null, (get, set, id: string) => {
  const todos = get(todosAtom);
  set(
    todosAtom,
    todos.map((t) =>
      t.id === id ? { ...t, completed: !t.completed } : t
    )
  );
});

Middleware und Erweiterungen

FeatureZustandJotaiRedux ToolkitSignals
PersistenzpersistatomWithStorageredux-persistManuell
DevToolsdevtoolsjotai-devtoolsRedux DevToolsBegrenzt
Async StateNativatomWithQueryRTK QueryManuell
Immerimmer middlewareatomWithImmerEingebautN/A
Undo/RedoManuellatomWithUndoredux-undoManuell

Server-seitiger State (SSR/RSC)

Mit dem Aufstieg von React Server Components ist SSR-Kompatibilität wichtig:

// Zustand - SSR mit initialem State
const useStore = create((set) => ({
  // ...
}));

// In Server Component
export default async function Page() {
  const data = await fetchData();
  return <ClientComponent initialData={data} />;
}

// In Client Component
function ClientComponent({ initialData }) {
  useEffect(() => {
    useStore.setState({ data: initialData });
  }, [initialData]);
}

// Jotai - Provider-basiert für SSR
function App({ initialState }) {
  return (
    <Provider>
      <HydrateAtoms initialValues={[[dataAtom, initialState]]}>
        <MyApp />
      </HydrateAtoms>
    </Provider>
  );
}

Person holding a React logo sticker, a popular JavaScript library

Wann welche Lösung wählen?

Zustand wählen wenn:

  • Sie eine einfache, pragmatische Lösung bevorzugen
  • Ihr Team neu im State Management ist
  • Sie von Redux migrieren wollen (ähnliche Konzepte, weniger Boilerplate)
  • Sie globalen State mit minimaler API brauchen
  • Performance wichtig ist, aber nicht kritisch

Ideale Anwendungsfälle:

  • SaaS-Dashboards
  • E-Commerce-Apps
  • Content-Management-Systeme

Jotai wählen wenn:

  • Sie atomaren, granularen State brauchen
  • Ihre App viele unabhängige State-Stücke hat
  • Sie React Suspense nutzen wollen
  • Performance durch selektives Re-Rendering kritisch ist

Ideale Anwendungsfälle:

  • Komplexe Formulare
  • Design-Tools (wie ein Color Picker)
  • Konfigurationsintensive Apps

Redux Toolkit wählen wenn:

  • Sie ein großes Team mit bestehender Redux-Erfahrung haben
  • Sie umfangreiche DevTools und Debugging brauchen
  • Ihre App einen komplexen, zentralisierten State hat
  • Sie RTK Query für Server-State nutzen wollen

Ideale Anwendungsfälle:

  • Enterprise-Anwendungen
  • Apps mit komplexen State-Transitionen
  • Projekte mit strikten Audit-Anforderungen

Signals wählen wenn:

  • Maximale Performance Ihr oberstes Ziel ist
  • Sie mit der experimentellen Natur leben können
  • Ihr Team reaktive Programmierung kennt
  • Sie ein Framework wie Preact oder Solid verwenden

Ideale Anwendungsfälle:

  • Echtzeit-Dashboards
  • Animations-intensive Apps
  • Gaming-UIs
  • Performance-kritische Anwendungen

Kombination mit Server State

In der Praxis brauchen die meisten Apps sowohl Client- als auch Server-State:

// TanStack Query für Server State + Zustand für Client State
import { useQuery } from "@tanstack/react-query";
import { create } from "zustand";

// Server State mit TanStack Query
function useTodos() {
  return useQuery({
    queryKey: ["todos"],
    queryFn: () => fetch("/api/todos").then((r) => r.json()),
  });
}

// Client State mit Zustand
const useUIStore = create((set) => ({
  selectedTodoId: null,
  isFilterOpen: false,
  setSelectedTodo: (id) => set({ selectedTodoId: id }),
  toggleFilter: () =>
    set((state) => ({ isFilterOpen: !state.isFilterOpen })),
}));

// Komponente die beides nutzt
function TodoList() {
  const { data: todos, isLoading } = useTodos();
  const { selectedTodoId, setSelectedTodo } = useUIStore();

  if (isLoading) return <Spinner />;

  return (
    <ul>
      {todos.map((todo) => (
        <li
          key={todo.id}
          className={todo.id === selectedTodoId ? "selected" : ""}
          onClick={() => setSelectedTodo(todo.id)}
        >
          {todo.text}
        </li>
      ))}
    </ul>
  );
}

Bright and colorful JavaScript code displayed on a computer screen

Migration zwischen State-Management-Lösungen

Von Redux zu Zustand

// Redux (vorher)
const todosSlice = createSlice({
  name: "todos",
  initialState: [],
  reducers: {
    add: (state, action) => { state.push(action.payload); },
    remove: (state, action) => state.filter(t => t.id !== action.payload),
  },
});

// Zustand (nachher)
const useTodoStore = create((set) => ({
  todos: [],
  add: (todo) => set((s) => ({ todos: [...s.todos, todo] })),
  remove: (id) => set((s) => ({ todos: s.todos.filter(t => t.id !== id) })),
}));

Von Zustand zu Jotai

// Zustand (vorher)
const useStore = create((set) => ({
  count: 0,
  name: "",
  increment: () => set((s) => ({ count: s.count + 1 })),
  setName: (name) => set({ name }),
}));

// Jotai (nachher)
const countAtom = atom(0);
const nameAtom = atom("");
const incrementAtom = atom(null, (get, set) => {
  set(countAtom, get(countAtom) + 1);
});

Best Practices 2026

1. Trennen Sie Client- und Server-State

Verwenden Sie TanStack Query oder SWR für Server-State und Zustand/Jotai/Redux nur für Client-State.

2. Halten Sie den State so lokal wie möglich

Nicht jeder State gehört in einen globalen Store. React-eigener State (useState, useReducer) ist oft ausreichend.

3. Verwenden Sie Selektoren

// Zustand: Selektoren vermeiden unnötige Re-Renders
const count = useStore((state) => state.count);
// NICHT: const { count } = useStore();

4. TypeScript ist Pflicht

Alle vier Lösungen unterstützen TypeScript hervorragend. Nutzen Sie es.

5. Testen Sie Ihren State

// Zustand Store testen
import { renderHook, act } from "@testing-library/react-hooks";

test("increment increases count", () => {
  const { result } = renderHook(() => useCounterStore());
  expect(result.current.count).toBe(0);
  act(() => result.current.increment());
  expect(result.current.count).toBe(1);
});

Fazit

2026 gibt es keine universell "beste" State-Management-Lösung. Die Wahl hängt von Ihrem Projekt, Team und Ihren Anforderungen ab:

  • Zustand ist der beste Allrounder und die sicherste Wahl für die meisten Projekte
  • Jotai glänzt bei granularem, atomarem State und React-Suspense-Integration
  • Redux Toolkit bleibt die Wahl für große Enterprise-Teams mit bestehender Redux-Infrastruktur
  • Signals ist der Performance-Champion, aber noch nicht vollständig standardisiert

Unser Tipp: Starten Sie mit Zustand. Es ist einfach zu lernen, performant und deckt 90% aller Anwendungsfälle ab. Wechseln Sie nur, wenn Sie eine spezifische Anforderung haben, die eine andere Lösung besser erfüllt.

Nutzen Sie unsere Developer Tools beim Entwickeln, etwa den JSON Formatter zum Debuggen von State-Objekten oder den Text Diff zum Vergleichen von State-Snapshots.

Verwandte Beiträge