Tu te souviens de la dernière fois où tu as passé une heure à configurer un store Redux avec ses action types, ses reducers, ses connecteurs et ses fichiers séparés pour stocker un panier de 3 articles ? Cette époque est derrière nous. En 2026, le state management React a mûri, et une librairie minuscule a imposé un standard de simplicité qui fait passer Redux pour une usine à gaz. Cette librairie, c’est Zustand.
Zustand n’est pas un « mini Redux » ni un clone allégé. Il repense la gestion d’état React en posant une question simple : pourquoi un store devrait-il ressembler à du code back-end avec des dispatchers et des reducers si le navigateur n’a pas besoin de cette bureaucratie ? La réponse tient dans une API triviale et dans la disparition du boilerplate qui encombrait nos composants. Si vous migrez vers une architecture hybride avec des server components React, la question du state management se tranche désormais sur la granularité, pas sur le poids du framework. Zustand est souvent le meilleur compromis.
Là où Redux et Context API coincent
React propose trois mécanismes natifs : le state local (useState), le contexte (Context API) et les reducers (useReducer). Dès qu’une application dépasse trois niveaux de composants, l’approche locale lâche. Prop drilling, perfs dégradées, logs illisibles. On passe à une solution globale.
Redux a régné pendant des années avec son architecture unidirectionnelle et son immutabilité stricte. Le problème, c’est le volume de code nécessaire. Chaque « feature » impose de créer un type d’action, un creator, un reducer, de les combiner, de configurer un provider, et finalement d’utiliser connect ou useSelector avec des vérifications d’égalité. Sur un projet à deux développeurs, cela représente des centaines de lignes avant même la première fonction métier. La version moderne de Redux Toolkit réduit cette friction, mais l’empreinte mentale reste élevée : il faut encore comprendre les slices, les thunks, les entités normalisées.
De son côté, l’API Context de React couplée à useReducer devient vite un goulot d’étranglement. Le moindre changement de valeur dans un contexte force le re-render de tous les composants enfants qui y sont abonnés, sauf à découper les contextes en morceaux, ce qui fait retomber dans une complexité comparable à Redux. Et en 2026, avec l’arrivée massive des Server Components, le contexte purement client montre ses limites car il ne traverse pas la frontière serveur/client sans un vrai store dédié.
Si vous avez déjà configuré une API route Next.js avec authentification et voulu stocker le token de session côté client, vous avez probablement pesté contre le boilerplate d’un slice Redux pour une simple chaîne de caractères.
Ce que Zustand change dans le cerveau du développeur
Zustand est un store. Il ne crée pas un écosystème d’event sourcing. Il ne vous oblige pas à mapper des actions sur des reducers. Il ne vous impose pas de dispatcher des intentions. Vous déclarez un état et des fonctions qui le modifient directement. Le tout dans un unique fichier, sans Provider englobant, sans décorateur.
La philosophie est radicale : le state management doit ressembler à du JavaScript ordinaire. Une fonction create importée depuis zustand reçoit un callback qui expose set (pour merger l’état) et éventuellement get. Ce callback retourne un objet qui mélange état et actions. Le hook useStore (ou tout autre nom que vous donnez) s’importe ensuite dans n’importe quel composant React et donne accès aux propriétés via un sélecteur. C’est tout.
Ce fonctionnement répond à une confusion tenace : beaucoup de développeurs confondent state (les données à un instant T), store (le conteneur qui détient le state et le rend observable) et management (les patterns et librairies qui organisent les flux de mise à jour). Redux impose une discipline forte basée sur le pattern flux ; Context API fournit un simple bus de données ; Zustand réunit le conteneur et la logique de mutation en un seul concept, sans friction.
Prenons un exemple direct. Un store panier e-commerce se crée ainsi :
import { create } from 'zustand';
const useCartStore = create((set) => ({
items: [],
total: 0,
addItem: (product) =>
set((state) => ({
items: [...state.items, product],
total: state.total + product.price,
})),
clear: () => set({ items: [], total: 0 }),
}));
Aucun fichier d’actions, aucun reducer, aucun sélecteur exporté. La fonction addItem est une simple flèche qui lit l’état courant via set et retourne un nouvel objet. Si vous aviez l’habitude d’utiliser un bundler moderne comme Vite, vous remarquez que ce fichier pèse moins de 500 octets et ne nécessite aucune configuration supplémentaire.
Dans un composant, on consomme le store avec le hook ainsi nommé :
function Cart() {
const total = useCartStore((state) => state.total);
const addItem = useCartStore((state) => state.addItem);
// ...
}
Le premier argument de useCartStore est un sélecteur. Si vous omettez le sélecteur, le composant se re-rend à chaque changement du store. Avec un sélecteur fin, seules les modifications de la tranche concernée déclenchent un re-render. Ce mécanisme de souscription granulaire est ce qui permet à Zustand d’atteindre une performance comparable à Redux avec bien moins de code.
Le flux create → import → set → use dans un vrai composant React
!A wooden desk with a React logo badge, a Zustand logo sticker, and a handwritten code snippet showing create, import, se
create côté store, hook nommé exporté (useFavStore), sélecteur fin côté composant. Pour éviter de réécrire le même sélecteur partout, on exporte des sélecteurs nommés : export const useFavCount = () => useFavStore((s) => s.count);
Le store reste un module JavaScript comme un autre. On le teste unitairement en appelant les setters, sans wrapper de test Redux. Si vous utilisez un modèle d’IA pour la programmation, générer un store Zustand avec ses actions et ses sélecteurs prend moins de 20 secondes en prompt.
Middleware, devtools et persist : l’écosystème qui évite de salir le store
Un des grands « oui mais » qu’on nous oppose souvent : « Zustand, c’est bien pour un POC, mais en production on a besoin de persistance locale, de logs, de time travel debugging. » Ce à quoi on répond : ces capacités existent, sous forme de middlewares, et elles s’enfilent comme des perles sans modifier la logique métier du store.
Zustand embarque trois middlewares officiels clés :
devtools: intègre le store dans l’extension Redux DevTools. On voit chaque transition d’état, on peut revenir en arrière. La syntaxe :const useMyStore = create(devtools((set) => ({ ... }))).persist: sauvegarde l’état dans lelocalStorage(ousessionStorage, ou AsyncStorage). Il suffit de wrapper le store avecpersist(config). On peut aussi choisir les clés à exclure.subscribe: une méthode disponible sur chaque store Zustand, qui permet d’attacher un listener d’état, utile pour synchroniser une autre librairie ou pour le logging.
L’enchaînement d’un store persistant, logué et compatible DevTools ressemble à cela :
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
const useAppStore = create(
devtools(
persist(
(set) => ({
darkMode: false,
toggle: () => set((s) => ({ darkMode: !s.darkMode })),
}),
{ name: 'app-storage' }
),
{ name: 'AppStore' }
)
);
On ajoute les préoccupations transversales (persistance, débogage) sans toucher à la logique métier. C’est l’inverse de Redux où chaque nouveau middleware nécessite une reconfiguration du store global et parfois des imports supplémentaires dans les slices.
Vous pouvez même ajouter immer pour écrire des mutations directes :
import { immer } from 'zustand/middleware/immer';
const useStore = create(
immer((set) => ({
nested: { count: 0 },
increment: () =>
set((state) => {
state.nested.count += 1;
}),
}))
);
Ça évite la syntaxe de spread à rallonge sur des objets profonds. Et ça reste compatible avec les types TypeScript les plus stricts (même si on ne parle pas d’ORM, l’inférence fonctionne bien).
State management en 2026 : pourquoi le comparatif Redux vs Context vs Zustand est plié
!Three small stone tablets labeled Redux, Context, Zustand, one broken in half, on a white surface, soft overhead light,
On entend encore des « ça dépend » quand on demande quel est le meilleur state management React.
Redux reste pertinent quand l’organisation exige une séparation stricte des responsabilités, une journalisation exhaustive des actions, ou un écosystème middleware hyper-spécialisé (Sagas, Redux-Observable). Mais ces cas sont devenus minoritaires avec l’adoption massive des architectures hybrides et de la logique métier poussée côté serveur. En 2026, une large part des applications React sont des coquilles client légères nourries par des API, ou des apps full-stack avec Next.js où l’état serveur est géré par React Query ou SWR. L’état global réellement nécessaire côté client se réduit aux préférences utilisateur, à l’authentification, à un panier, à quelques flags d’UI. Pour ces besoins, Redux est un marteau-piqueur sur une punaise.
Context API a l’avantage d’être natif, sans dépendance. Mais dès que l’état devient complexe ou fréquemment mis à jour, le manque de sélecteurs intégrés provoque des re-renders en cascade. Certains développeurs contournent le problème avec des patterns de sélection externes, mais cela revient à réimplémenter un tiers de Zustand à la main.
Zustand combine la simplicité d’un module JavaScript et des sélecteurs performants. Sa taille (moins de 2 Ko) le rend compatible avec les exigences de crawl budget et de performance Web : un site e-commerce avec une page produit interactive peut hydrater un store Zustand en quelques millisecondes, là où le même state Redux nécessite un chunk supplémentaire et un provider encombrant.
Le match se résume ainsi :
| Critère | Redux Toolkit | Context + useReducer | Zustand |
|---|---|---|---|
| Boilerplate minimal | ⚠️ slices, thunks | ❌ manque sélecteurs | ✅ |
| Performance (sélecteurs) | ✅ | ❌ | ✅ |
| Middleware intégré | ✅ | ❌ | ✅ |
| Taille bundle | ~11 Ko | 0 Ko (natif) | < 2 Ko |
| Server Components ready | ✅ (mais lourd) | ⚠️ | ✅ (léger) |
Si vous avez un projet Next.js vs Remix, sachez que Zustand fonctionne de la même manière dans les deux cadres. Après avoir déployé une application Node.js sur Vercel, vous apprécierez qu’il n’y ait aucun contexte global à faire fuiter entre le serveur et le client.
Quand prendre Zustand (et quand s’en passer)
La question « quand utiliser Zustand » se traite en deux mots : presque toujours. Plus précisément, adoptez Zustand quand vous avez besoin d’un état partagé entre plusieurs composants distants sans lien de parenté direct, et que cet état doit déclencher des re-renders sélectifs. C’est le cas de la quasi-totalité des applications frontales modernes, surtout si vous pratiquez déjà le code splitting ou l’architecture micro-frontends.
Il y a tout de même des situations où Zustand n’est pas le choix optimal. Sur un dashboard financier avec des chaînes d’actions asynchrones imbriquées et des mises à jour temps réel de multiples sources, la rigueur imposée par Redux Toolkit et ses middlewares (Redux-Saga, Redux-Observable) peut avoir un avantage de maintenabilité pour une grosse équipe. Si l’état client se résume au « server state » géré par React Query ou SWR, vous pouvez même vous passer complètement de librairie. Et si l’équipe maîtrise Redux sur le bout des doigts avec un existant colossal, la migration coûte plus cher que le gain. Sur un nouveau projet, Zustand reste le bon point de départ.
Sous le capot, Zustand repose sur le pattern observable. Le store créé par create maintient son état dans une closure et notifie les composants abonnés quand la tranche sélectionnée change. Le hook useSyncExternalStore de React (utilisé en interne) assure la cohérence avec le mode concurrent de React 18+.
Questions fréquentes
Zustand, c’est quoi exactement ?
Zustand est une librairie de state management minimaliste pour React. Elle tient dans un seul fichier, ne nécessite aucun Provider, et expose un hook personnalisé que vous utilisez directement dans vos composants. Elle couvre la gestion d’état global avec des sélecteurs performants et un écosystème de middlewares facultatifs.
Pourquoi utiliser Zustand plutôt que Context API ou Redux ?
Parce que vous écrivez moins de code pour une meilleure performance. Context API oblige à gérer manuellement les re-renders ou à multiplier les contextes ; Redux, même avec Toolkit, impose un moule à actions et reducers. Zustand offre la même puissance avec un dixième du boilerplate et une courbe d’apprentissage quasi nulle.
Quels sont les principaux bénéfices de Zustand ?
Un développement accéléré (moins de fichiers, moins de concepts), des performances optimales grâce à des sélecteurs granulaires, une taille de bundle infime, et la possibilité d’ajouter des middlewares comme la persistance locale, le time-travel debugging ou l’immutabilité simplifiée sans changer la logique métier.
Comment choisir entre Zustand et une autre librairie de state management ?
Deux questions suffisent. Avez-vous besoin d’un état global client au-delà du simple server state ? Si oui, ce sera Zustand la plupart du temps. Votre équipe a-t-elle des exigences de traçabilité ultra-strictes ou un existant Redux colossal sans budget de migration ? Dans ce cas, Redux Toolkit se justifie. Autrement, vous vous compliquez la vie.
Votre recommandation sur zustand
Quelques questions rapides pour adapter la recommandation à votre cas.
Merci, voici notre conseil personnalisé.
D'après vos réponses, le mieux est de reprendre l'article ci-dessus en focalisant sur les passages qui parlent de votre situation : c'est là que se trouvent les recommandations les plus concrètes pour vous. Bonne lecture !