14 juillet 2026 · Nuxt · Gestion d'état

Gestion d'état dans Nuxt : useState, bonnes pratiques et pièges

useState, partage compatible SSR, pollution inter-requêtes, quand sortir Pinia : les règles pour gérer l'état d'une application Nuxt sans se tirer une balle dans le pied.

En Vue côté client, on partage souvent un état en déclarant un ref dans un module et en l'important où on veut. Ça marche très bien dans le navigateur, et ça casse silencieusement dès qu'on passe au rendu serveur. Comprendre pourquoi, c'est comprendre toute la gestion d'état de Nuxt.

Le piège du ref partagé en module

// state.ts  - À NE PAS FAIRE en SSR
import { ref } from 'vue'
export const utilisateur = ref(null)

Côté client, ce ref est unique : un seul navigateur, un seul état. Côté serveur, le processus Node est partagé entre tous les visiteurs. Ce ref unique devient alors un état commun à toutes les requêtes simultanées : l'utilisateur A voit les données de l'utilisateur B. C'est une fuite de données inter-requêtes, et elle est très difficile à repérer parce qu'elle n'apparaît pas en développement local avec un seul onglet.

useState : l'état partagé fait pour le SSR

La réponse de Nuxt est useState. Il crée un état réactif isolé par requête côté serveur, et correctement transféré (sérialisé puis hydraté) vers le client :

// app/composables/useUtilisateur.ts
export function useUtilisateur() {
  return useState('utilisateur', () => null)
}

Deux détails comptent. Le premier argument est une clé unique : deux appels avec la même clé partagent le même état, c'est le mécanisme de partage. Le second argument est une fonction d'initialisation, pas une valeur directe : elle n'est appelée qu'une fois, au bon moment du cycle SSR.

L'envelopper dans un composable useUtilisateur() évite de répéter la clé partout et centralise le point d'accès. Si un jour la clé change, un seul fichier bouge.

useFetch et useAsyncData : l'état, c'est aussi les données

La majorité de l'état d'une application, ce sont des données distantes. Ne les gérez pas à la main avec un ref plus un onMounted plus un try/catch. useFetch fait tout cela, et de façon compatible SSR :

<script setup lang="ts">
const { data: articles, status, error } = await useFetch('/api/articles')
</script>

Il récupère les données côté serveur au premier rendu (donc le HTML arrive déjà rempli, bon pour le SEO), les transfère au client sans refetch, et gère le cache et la déduplication via une clé. Deux composants qui appellent useFetch avec la même URL ne déclenchent qu'une seule requête. Traiter les données comme un état géré par le framework, plutôt que comme du code impératif, élimine une classe entière de bugs.

Quand sortir Pinia

useState couvre l'état simple et partagé. Passez à Pinia (le store officiel de l'écosystème Vue) quand l'état devient un domaine à part entière :

  • de la logique métier autour de l'état (actions, règles, validations) et pas seulement une valeur ;
  • des valeurs dérivées nombreuses (des getters qui se recalculent) ;
  • un besoin de structurer par modules (panier, session, préférences) avec chacun son état, ses actions, ses getters ;
  • l'envie d'outils de débogage dédiés et d'un point d'entrée testable isolément.

Le critère n'est pas la taille de l'application mais la nature de l'état : une poignée de valeurs partagées, useState suffit ; un domaine avec de la logique, Pinia paie son coût.

Quelques pièges concrets

  • Ne mettez pas de valeur non sérialisable dans useState. L'état transite du serveur au client en JSON. Une instance de classe, une fonction ou une Date ne survivent pas au voyage. Stockez des données simples et reconstruisez les objets riches côté client si besoin.
  • Attention aux clés qui se chevauchent. Deux useState('data', ...) dans deux contextes différents partagent le même état par accident. Nommez les clés comme un espace de noms : 'panier', 'session-user', 'prefs-theme'.
  • N'accédez pas à window ou localStorage à l'initialisation. Ce code s'exécute aussi côté serveur, où ces objets n'existent pas. Pour un état persistant côté navigateur, useCookie fonctionne des deux côtés ; sinon, gardez l'accès au stockage dans onMounted.

La leçon

La gestion d'état en Nuxt se résume à une prise de conscience : votre code tourne côté serveur autant que côté client, et un état mal isolé fuit entre les requêtes. À partir de là, tout s'ordonne : useState pour l'état partagé simple, useFetch pour les données distantes, Pinia quand l'état devient un domaine. Le ref global de module, lui, reste dans les projets purement client. Choisissez l'outil selon la portée de l'état, pas selon l'habitude.

← Tous les articles