Comment résoudre l’erreur “Hydration failed because the initial UI does not match” dans Next.js [Guide mis à jour 2026]

スポンサーリンク

Comment résoudre l’erreur “Hydration failed because the initial UI does not match” dans Next.js [Guide mis à jour 2026]

Si vous développez avec Next.js et que l’erreur “Hydration failed because the initial UI does not match what was rendered on the server” apparaît soudainement, vous n’êtes pas seul. Cette erreur est particulièrement fréquente dans les environnements React 19 et Next.js 15 avec App Router, avec des milliers de commentaires sur GitHub Discussions et constamment en tendance sur Stack Overflow. Cet article fournit un guide complet mis à jour pour 2026 pour identifier les causes et appliquer des solutions spécifiques, avec des exemples de code concrets.

Qu’est-ce que cette erreur ? Symptômes rencontrés

Une erreur d’hydratation (Hydration Error) dans Next.js se produit lorsque le HTML rendu côté serveur ne correspond pas au HTML que React tente de rendre côté client lors du rendu initial.

Concrètement, les messages d’erreur suivants apparaissent dans la console de développement de votre navigateur :

Unhandled Runtime Error
Error: Hydration failed because the initial UI does not match what was rendered on the server.

Vous pouvez aussi voir ces variantes :

Error: Text content does not match server-rendered HTML.
Error: There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.

Lorsque cette erreur se produit, Next.js abandonne le résultat du rendu côté serveur (SSR) et re-rend la page entière côté client. Cela entraîne plusieurs conséquences graves :

  • Dégradation significative des performances : Tous les avantages du SSR sont perdus, rendant le chargement initial plus lent
  • Impact négatif sur le SEO : Les robots d’indexation des moteurs de recherche pourraient ne pas recevoir le HTML correct
  • Mauvaise expérience utilisateur : Un scintillement d’écran (flash de contenu) se produit
  • Expérience développeur dégradée : La console est remplie d’erreurs, facilitant l’oubli d’autres problèmes

Dans Next.js 15 combiné avec React 19, les vérifications de cohérence de l’hydratation sont devenues plus strictes, et les cas qui n’étaient auparavant que des avertissements sont maintenant traités comme des erreurs.

Causes de cette erreur

Cause 1 : Utilisation d’APIs exclusives au navigateur (window / localStorage / document)

La cause la plus fréquente est le référencement direct d’APIs exclusives au navigateur comme window, localStorage et document pendant le rendu. Ces objets n’existent pas côté serveur.

Côté serveur, ces objets sont undefined. Par conséquent, utiliser des branches conditionnelles comme typeof window !== 'undefined' dans la logique de rendu provoque des sorties différentes entre serveur et client, déclenchant des erreurs d’hydratation.

// MAUVAIS : Cela provoque une erreur d'hydratation
function MyComponent() {
  const isClient = typeof window !== 'undefined';
  return <div>{isClient ? 'Client' : 'Serveur'}</div>;
}

Cause 2 : Valeurs non déterministes comme les horodatages et les nombres aléatoires

L’utilisation de fonctions qui retournent des valeurs différentes à chaque appel, comme new Date() ou Math.random(), pendant le rendu provoque des incohérences entre la sortie du serveur et du client.

// MAUVAIS : L'horodatage diffère entre serveur et client
function Clock() {
  return <p>Heure actuelle : {new Date().toLocaleTimeString()}</p>;
}

Il y a toujours un décalage temporel entre le moment où le serveur effectue le rendu et celui où le client hydrate, donc l’heure affichée sera naturellement différente, résultant en une erreur de non-correspondance du contenu texte.

Cause 3 : Structure d’imbrication HTML invalide

Des structures d’imbrication invalides qui violent les spécifications HTML (par exemple, placer un <div> à l’intérieur d’une balise <p>) provoquent la correction automatique du DOM par le parseur HTML du navigateur, créant une structure DOM différente du HTML rendu sur le serveur.

// MAUVAIS : <div> ne peut pas être placé dans <p>
function BadNesting() {
  return (
    <p>
      Texte
      <div>Ceci est une imbrication invalide</div>
    </p>
  );
}

Le navigateur corrige automatiquement ce HTML invalide, causant une non-correspondance entre le HTML envoyé par le serveur et le DOM du navigateur.

Cause 4 : Extensions de navigateur modifiant le DOM

Des extensions de navigateur comme Colorzilla et Grammarly peuvent injecter des attributs ou des éléments HTML dans le DOM de la page. Par exemple, Colorzilla ajoute un attribut cz-shortcut-listen="true" à la balise <body>. Cela a été signalé comme particulièrement problématique dans les environnements Next.js 15 + React 19.

Cause 5 : Scripts tiers et incohérences de contenu CMS

Les scripts externes (balises publicitaires, analytics, etc.) qui modifient le DOM, ou le contenu HTML provenant d’un CMS contenant des structures d’imbrication invalides, peuvent déclencher des erreurs d’hydratation.

Solution 1 : Isoler la logique exclusive au client avec le hook useEffect (Recommandé)

La solution la plus efficace et recommandée est de déplacer la logique dépendante du navigateur dans le hook useEffect et de ne produire que des valeurs déterministes lors du rendu initial.

Étape 1 : Implémenter le patron de gestion d’état client

D’abord, définissez un State avec la même valeur initiale pour le serveur et le client, puis mettez-le à jour vers la valeur côté client avec useEffect.

'use client';

import { useState, useEffect } from 'react';

function ThemeProvider({ children }) {
  // Utiliser une valeur sûre pour le serveur comme état initial
  const [theme, setTheme] = useState('light');
  const [mounted, setMounted] = useState(false);

  useEffect(() => {
    // Accéder à localStorage uniquement côté client
    const savedTheme = localStorage.getItem('theme') || 'light';
    setTheme(savedTheme);
    setMounted(true);
  }, []);

  // Avant le montage, retourner la même sortie que le serveur
  if (!mounted) {
    return <div data-theme="light">{children}</div>;
  }

  return <div data-theme={theme}>{children}</div>;
}

Étape 2 : Définir les horodatages et valeurs aléatoires dans useEffect

'use client';

import { useState, useEffect } from 'react';

function Clock() {
  const [currentTime, setCurrentTime] = useState('');

  useEffect(() => {
    const updateTime = () => {
      setCurrentTime(new Date().toLocaleTimeString());
    };
    updateTime();
    const timer = setInterval(updateTime, 1000);
    return () => clearInterval(timer);
  }, []);

  // Afficher une chaîne vide lors du rendu initial (correspond au serveur)
  return <p>Heure actuelle : {currentTime || 'Chargement...'}</p>;
}

Étape 3 : Rendre réutilisable avec un hook personnalisé

Si vous utilisez ce patron dans plusieurs composants, extrayez-le dans un hook personnalisé.

'use client';

import { useState, useEffect } from 'react';

// Hook personnalisé pour gérer l'état de montage
function useHasMounted() {
  const [hasMounted, setHasMounted] = useState(false);
  useEffect(() => {
    setHasMounted(true);
  }, []);
  return hasMounted;
}

// Exemple d'utilisation
function ClientOnlyComponent() {
  const hasMounted = useHasMounted();

  if (!hasMounted) {
    return <div>Chargement...</div>;
  }

  return <div>Largeur de la fenêtre : {window.innerWidth}px</div>;
}

Notes importantes

  • useEffect n’est pas exécuté côté serveur, les APIs du navigateur peuvent donc être utilisées en toute sécurité à l’intérieur
  • Affichez un placeholder ou une UI de secours lors du rendu initial pour maintenir l’expérience utilisateur
  • Ce patron peut brièvement afficher un placeholder, mais c’est bien plus léger que le re-rendu de toute la page causé par les erreurs d’hydratation

Solution 2 : Désactiver le SSR avec next/dynamic

Pour les composants difficiles à gérer avec le patron useEffect (par exemple, les bibliothèques tierces fortement dépendantes des APIs du navigateur), désactiver entièrement le SSR avec next/dynamic est efficace.

import dynamic from 'next/dynamic';

// Importer avec SSR désactivé
const MapComponent = dynamic(
  () => import('../components/Map'),
  {
    ssr: false,
    loading: () => <div className="map-placeholder">Chargement de la carte...</div>
  }
);

// Exemple d'utilisation
function LocationPage() {
  return (
    <div>
      <h1>Emplacement du magasin</h1>
      <MapComponent />
    </div>
  );
}

Cette méthode est particulièrement efficace dans les cas suivants :

  • Bibliothèques de cartes (Leaflet, Google Maps, etc.) : Car elles dépendent de l’objet window
  • Éditeurs de texte riche (Quill, TipTap, etc.) : Car ils manipulent directement l’objet document
  • Bibliothèques de graphiques (Chart.js, D3.js, etc.) : Car elles effectuent des opérations DOM SVG ou Canvas

Cependant, les composants avec SSR désactivé ne sont pas inclus dans le HTML initial, donc évitez cela pour le contenu critique pour le SEO. La meilleure pratique est de limiter son utilisation aux éléments interactifs autres que le contenu principal (cartes, graphiques, éditeurs, etc.).

Solution 3 : Corriger l’imbrication HTML et utilisation appropriée de suppressHydrationWarning

Comme solution avancée, vous pouvez corriger les structures d’imbrication HTML et utiliser suppressHydrationWarning pour les cas inévitables.

Corriger l’imbrication HTML invalide

D’abord, vérifiez s’il y a des imbrications HTML invalides. Voici les patterns invalides courants et leurs corrections :

// MAUVAIS : <div> dans <p>
<p>Texte<div>Élément de bloc</div></p>

// BON : Changer pour <div>
<div>Texte<div>Élément de bloc</div></div>

// MAUVAIS : <a> dans <a>
<a href="/parent">
  Lien parent
  <a href="/enfant">Lien enfant</a>
</a>

// BON : Supprimer l'imbrication
<div>
  <a href="/parent">Lien parent</a>
  <a href="/enfant">Lien enfant</a>
</div>

Utilisation limitée de suppressHydrationWarning

Pour les petits éléments où les différences de valeur serveur-client sont inévitables (comme l’affichage d’horodatages), vous pouvez utiliser suppressHydrationWarning.

// Utiliser uniquement quand les différences sont acceptables, comme les horodatages
<time suppressHydrationWarning>
  {new Date().toLocaleDateString()}
</time>

Note importante : suppressHydrationWarning ne fait que masquer l’erreur ; il ne résout pas le problème sous-jacent. Il ne s’applique qu’à un seul niveau de nœuds texte, et les non-correspondances d’éléments enfants sont toujours détectées. Utilisez-le avec parcimonie et uniquement lorsque c’est absolument nécessaire.

Gestion des extensions de navigateur

Si les extensions de navigateur sont la cause, vous pouvez ajouter suppressHydrationWarning à la balise <body>.

// app/layout.tsx
export default function RootLayout({ children }) {
  return (
    <html lang="fr">
      <body suppressHydrationWarning>{children}</body>
    </html>
  );
}

Cela supprime les avertissements causés par les attributs que les extensions ajoutent au body. Cependant, les non-correspondances dans les enfants directs du body sont toujours détectées.

Comment prévenir cette erreur

Pour prévenir proactivement les erreurs d’hydratation, intégrez ces mesures préventives dans votre workflow de développement quotidien.

1. Garder le rendu initial “déterministe”

Le principe le plus important est de garantir une sortie identique pour les rendus initiaux du serveur et du client. Les valeurs dépendantes de l’environnement (APIs du navigateur, horodatages, nombres aléatoires, etc.) doivent toujours être placées dans useEffect.

2. Séparer clairement Server Components et Client Components

Dans l’App Router de Next.js 15, les composants sont des Server Components par défaut. Lorsque vous utilisez des APIs du navigateur ou des hooks React (useState, useEffect, etc.), ajoutez toujours la directive 'use client' en haut du fichier.

3. Exploiter les règles ESLint

eslint-plugin-react inclut des règles pour détecter l’imbrication HTML invalide (comme jsx-no-invalid-html-nesting). Intégrez-les à votre pipeline CI pour des vérifications automatiques.

4. Tests réguliers

Testez périodiquement en mode navigation privée ou avec les extensions de navigateur désactivées pour isoler les erreurs liées aux extensions.

5. Design responsive piloté par CSS

Au lieu de bifurquer le balisage selon la taille du viewport, utilisez les media queries CSS ou display: none pour basculer la visibilité, en gardant la structure HTML cohérente entre serveur et client.

// MAUVAIS : Bifurcation viewport en JS
{isMobile ? <MobileNav /> : <DesktopNav />}

// BON : Basculement piloté par CSS
<MobileNav className="block md:hidden" />
<DesktopNav className="hidden md:block" />

Résumé

L’erreur d’hydratation Next.js “Hydration failed because the initial UI does not match what was rendered on the server” se produit en raison de non-correspondances entre SSR/SSG et le rendu côté client, ce qui en fait l’une des erreurs les plus fréquemment rencontrées dans le développement Next.js.

Points clés :

  • Le problème central est “des résultats de rendu initial différents entre serveur et client”
  • Causes les plus courantes : utilisation directe d’APIs exclusives au navigateur, rendu de valeurs non déterministes, imbrication HTML invalide
  • Solutions recommandées : rendu par étapes avec useEffect, désactivation du SSR avec next/dynamic si nécessaire
  • suppressHydrationWarning doit être utilisé en dernier recours, avec une portée limitée
  • Prévention : garder les rendus initiaux déterministes, séparer clairement Server/Client Components

Si ces solutions ne résolvent pas le problème, vérifiez les points suivants :

  1. Mettez à jour Next.js et React vers les dernières versions
  2. Supprimez les dossiers node_modules et .next et reconstruisez
  3. Recherchez des cas similaires sur les GitHub Discussions de Next.js (https://github.com/vercel/next.js/discussions)
  4. Créez une reproduction minimale et signalez-la comme Issue

Références

コメント

タイトルとURLをコピーしました