# Comment corriger les erreurs d'hydratation React causées par l'IA

> Les agents d'IA génèrent des composants React qui affichent un HTML différent côté serveur et côté client, provoquant des erreurs d'hydratation et une interface cassée au premier chargement.

**Type:** Failure  
**Tools:** Cursor, Claude Code, Codex, Windsurf  
**Stack:** Next.js, Astro, TypeScript  
**Updated:** 2026-06-08

---

L'agent écrit des composants qui produisent un HTML différent côté serveur et côté client — dates, valeurs aléatoires, vérifications `window` — ce qui amène React à générer une erreur d'hydratation et à réafficher tout l'arbre au chargement.

## Le symptôme

Un composant lit `Date.now()`, `Math.random()`, ou `window` lors du rendu, produisant une sortie différente côté serveur vs côté client.

```tsx
// WRONG — hydration mismatch
export function Timestamp() {
  return <p>Page loaded at: {new Date().toLocaleTimeString()}</p>;
  // Server renders "10:00:00 AM", client renders "10:00:01 AM" — mismatch
}

// WRONG — conditional on window
export function ThemeIcon() {
  const isDark = typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches;
  return <span>{isDark ? "🌙" : "☀️"}</span>;
  // Server: always renders "☀️"; client: may render "🌙" — mismatch
}
```

La console du navigateur affiche :

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

## Pourquoi cela se produit

L'agent écrit des composants qui semblent corrects isolément. Il ne simule pas deux passes de rendu séparées (SSR + hydratation client) et ne sait pas que toute valeur différente entre les deux brisera l'hydratation.

## Comment le repérer

- `Date.now()`, `new Date()`, `Math.random()` appelés directement dans JSX ou lors du rendu sans être dans un initialiseur `useEffect` ou `useState`.
- Les gardes `typeof window !== "undefined"` dans le retour du rendu — le serveur prend toujours la branche `false`.
- Lecture de `localStorage`, `sessionStorage`, ou `navigator` pendant le rendu.
- Balises HTML ou valeurs d'attributs différentes entre la sortie serveur et client (visible dans l'avertissement d'hydratation de React DevTools).

## Comment le corriger

Reportez les valeurs propres au navigateur après le montage, ou générez des valeurs stables côté serveur et transmettez-les en tant que props.

```tsx
// CORRECT — defer client-only value to after mount
"use client";
import { useState, useEffect } from "react";

export function Timestamp() {
  const [time, setTime] = useState<string | null>(null);

  useEffect(() => {
    setTime(new Date().toLocaleTimeString());
  }, []);

  if (!time) return <p>Loading time…</p>; // matches server output
  return <p>Page loaded at: {time}</p>;
}
```

```tsx
// CORRECT — theme icon: use CSS / data attribute, avoid JS render branch
// Set data-theme on <html> in a blocking inline script (not React)
// Then use CSS: [data-theme="dark"] .icon { content: "🌙" }

// Or use Next.js next-themes which handles SSR safely:
import { useTheme } from "next-themes";
import { useEffect, useState } from "react";

export function ThemeIcon() {
  const { resolvedTheme } = useTheme();
  const [mounted, setMounted] = useState(false);
  useEffect(() => setMounted(true), []);
  if (!mounted) return <span style={{ width: 24 }} />; // stable placeholder
  return <span>{resolvedTheme === "dark" ? "🌙" : "☀️"}</span>;
}
```

```txt
[ ] No Date.now() / Math.random() / new Date() called directly during render
[ ] No typeof window / localStorage / navigator accessed during render
[ ] Browser-only state initializes to null/undefined, set in useEffect
[ ] Render a stable placeholder (same as server output) until after mount
[ ] Use suppressHydrationWarning only on elements where the mismatch is intentional (e.g. timestamp)
[ ] Run "next build && next start" and check browser console for hydration errors
```

## Prompt de correction

```txt title="Fix Prompt"
This component causes a React hydration mismatch because it reads browser-only
values (Date, window, localStorage, Math.random) during render. Fix it: move
all browser-only reads into a useEffect that sets state after mount, render a
stable placeholder on first render that matches what the server produces, and
never conditionally return different JSX trees based on typeof window. Explain
why each change prevents the mismatch.
```

## Test

```bash
# Build and start, then check for hydration errors with curl diff
next build 2>&1 | grep -i "hydrat\|mismatch" && echo "FAIL: hydration error in build" || echo "Build OK"

# Runtime: open browser console after next start and look for hydration warnings
next start &
sleep 3
curl -s http://localhost:3000 | grep -i "data-nextjs-scroll-focus-boundary" > /dev/null && echo "Server rendered OK"
```