{
  "id": "ai-creates-hydration-mismatches",
  "type": "failures",
  "category": "failures",
  "locale": "fr",
  "url": "/fr/failures/ai-creates-hydration-mismatches",
  "title": "Comment corriger les erreurs d'hydratation React causées par l'IA",
  "description": "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.",
  "tools": [
    "Cursor",
    "Claude Code",
    "Codex",
    "Windsurf"
  ],
  "stack": [
    "Next.js",
    "Astro",
    "TypeScript"
  ],
  "tags": [
    "hydration",
    "react",
    "nextjs"
  ],
  "difficulty": null,
  "updated": "2026-06-08",
  "markdown": "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.\n\n## Le symptôme\n\nUn composant lit `Date.now()`, `Math.random()`, ou `window` lors du rendu, produisant une sortie différente côté serveur vs côté client.\n\n```tsx\n// WRONG — hydration mismatch\nexport function Timestamp() {\n  return <p>Page loaded at: {new Date().toLocaleTimeString()}</p>;\n  // Server renders \"10:00:00 AM\", client renders \"10:00:01 AM\" — mismatch\n}\n\n// WRONG — conditional on window\nexport function ThemeIcon() {\n  const isDark = typeof window !== \"undefined\" && window.matchMedia(\"(prefers-color-scheme: dark)\").matches;\n  return <span>{isDark ? \"🌙\" : \"☀️\"}</span>;\n  // Server: always renders \"☀️\"; client: may render \"🌙\" — mismatch\n}\n```\n\nLa console du navigateur affiche :\n\n```\nError: Hydration failed because the initial UI does not match what was rendered\non the server.\n```\n\n## Pourquoi cela se produit\n\nL'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.\n\n## Comment le repérer\n\n- `Date.now()`, `new Date()`, `Math.random()` appelés directement dans JSX ou lors du rendu sans être dans un initialiseur `useEffect` ou `useState`.\n- Les gardes `typeof window !== \"undefined\"` dans le retour du rendu — le serveur prend toujours la branche `false`.\n- Lecture de `localStorage`, `sessionStorage`, ou `navigator` pendant le rendu.\n- Balises HTML ou valeurs d'attributs différentes entre la sortie serveur et client (visible dans l'avertissement d'hydratation de React DevTools).\n\n## Comment le corriger\n\nReportez 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.\n\n```tsx\n// CORRECT — defer client-only value to after mount\n\"use client\";\nimport { useState, useEffect } from \"react\";\n\nexport function Timestamp() {\n  const [time, setTime] = useState<string | null>(null);\n\n  useEffect(() => {\n    setTime(new Date().toLocaleTimeString());\n  }, []);\n\n  if (!time) return <p>Loading time…</p>; // matches server output\n  return <p>Page loaded at: {time}</p>;\n}\n```\n\n```tsx\n// CORRECT — theme icon: use CSS / data attribute, avoid JS render branch\n// Set data-theme on <html> in a blocking inline script (not React)\n// Then use CSS: [data-theme=\"dark\"] .icon { content: \"🌙\" }\n\n// Or use Next.js next-themes which handles SSR safely:\nimport { useTheme } from \"next-themes\";\nimport { useEffect, useState } from \"react\";\n\nexport function ThemeIcon() {\n  const { resolvedTheme } = useTheme();\n  const [mounted, setMounted] = useState(false);\n  useEffect(() => setMounted(true), []);\n  if (!mounted) return <span style={{ width: 24 }} />; // stable placeholder\n  return <span>{resolvedTheme === \"dark\" ? \"🌙\" : \"☀️\"}</span>;\n}\n```\n\n```txt\n[ ] No Date.now() / Math.random() / new Date() called directly during render\n[ ] No typeof window / localStorage / navigator accessed during render\n[ ] Browser-only state initializes to null/undefined, set in useEffect\n[ ] Render a stable placeholder (same as server output) until after mount\n[ ] Use suppressHydrationWarning only on elements where the mismatch is intentional (e.g. timestamp)\n[ ] Run \"next build && next start\" and check browser console for hydration errors\n```\n\n## Prompt de correction\n\n```txt title=\"Fix Prompt\"\nThis component causes a React hydration mismatch because it reads browser-only\nvalues (Date, window, localStorage, Math.random) during render. Fix it: move\nall browser-only reads into a useEffect that sets state after mount, render a\nstable placeholder on first render that matches what the server produces, and\nnever conditionally return different JSX trees based on typeof window. Explain\nwhy each change prevents the mismatch.\n```\n\n## Test\n\n```bash\n# Build and start, then check for hydration errors with curl diff\nnext build 2>&1 | grep -i \"hydrat\\|mismatch\" && echo \"FAIL: hydration error in build\" || echo \"Build OK\"\n\n# Runtime: open browser console after next start and look for hydration warnings\nnext start &\nsleep 3\ncurl -s http://localhost:3000 | grep -i \"data-nextjs-scroll-focus-boundary\" > /dev/null && echo \"Server rendered OK\"\n```"
}