{
  "id": "ai-creates-hydration-mismatches",
  "type": "failures",
  "category": "failures",
  "locale": "es",
  "url": "/es/failures/ai-creates-hydration-mismatches",
  "title": "Cómo solucionar los errores de hidratación en React causados por IA",
  "description": "Los agentes de IA generan componentes de React que renderizan HTML diferente en el servidor y el cliente, lo que provoca errores de hidratación y una interfaz rota en la primera carga.",
  "tools": [
    "Cursor",
    "Claude Code",
    "Codex",
    "Windsurf"
  ],
  "stack": [
    "Next.js",
    "Astro",
    "TypeScript"
  ],
  "tags": [
    "hydration",
    "react",
    "nextjs"
  ],
  "difficulty": null,
  "updated": "2026-06-08",
  "markdown": "El agente escribe componentes que producen HTML diferente en el servidor y el\ncliente — fechas, valores aleatorios, comprobaciones de `window` — lo que hace que React lance un\nerror de hidratación y vuelva a renderizar todo el árbol al cargar.\n\n## El síntoma\n\nUn componente lee `Date.now()`, `Math.random()` o `window` durante el renderizado,\nproduciendo una salida diferente en el servidor y en el cliente.\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 consola del navegador muestra:\n\n```\nError: Hydration failed because the initial UI does not match what was rendered\non the server.\n```\n\n## Por qué ocurre\n\nEl agente escribe componentes que parecen correctos de forma aislada. No simula\ndos pases de renderizado separados (SSR + hidratación del cliente) y no sabe que cualquier\nvalor que difiera entre ambos romperá la hidratación.\n\n## Cómo detectarlo\n\n- `Date.now()`, `new Date()`, `Math.random()` llamados directamente en JSX o durante\n  el renderizado sin estar dentro de un inicializador de `useEffect` o `useState`.\n- Guardias `typeof window !== \"undefined\"` dentro del retorno del renderizado — el servidor\n  siempre toma la rama `false`.\n- Lectura de `localStorage`, `sessionStorage` o `navigator` durante el renderizado.\n- Etiquetas HTML o valores de atributos diferentes entre la salida del servidor y el cliente\n  (visible en la advertencia de hidratación de React DevTools).\n\n## Cómo solucionarlo\n\nDiferir los valores solo del navegador hasta después del montaje, o generar valores estables en el servidor\ny pasarlos como 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 corrección\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## Prueba\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```"
}