# Como Corrigir Incompatibilidades de Hidratação React Causadas por IA

> Agentes de IA geram componentes React que renderizam HTML diferente no servidor e no cliente, causando erros de hidratação e UI quebrada no primeiro carregamento.

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

---

O agente escreve componentes que produzem HTML diferente no servidor em comparação com o
cliente — datas, valores aleatórios, verificações de `window` — fazendo com que o React lance um
erro de incompatibilidade de hidratação e re-renderize toda a árvore no carregamento.

## O sintoma

Um componente lê `Date.now()`, `Math.random()` ou `window` durante a renderização,
produzindo saída diferente no lado do servidor vs. lado do cliente.

```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
}
```

O console do navegador mostra:

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

## Por que isso acontece

O agente escreve componentes que parecem corretos isoladamente. Ele não simula
dois passes de renderização separados (SSR + hidratação do cliente) e não sabe que qualquer
valor que difira entre os dois quebrará a hidratação.

## Como identificar

- `Date.now()`, `new Date()`, `Math.random()` chamados diretamente no JSX ou durante
  a renderização sem estarem dentro de um inicializador de `useEffect` ou `useState`.
- Guardas `typeof window !== "undefined"` dentro do retorno da renderização — o servidor
  sempre pega o ramo `false`.
- `localStorage`, `sessionStorage` ou `navigator` lidos durante a renderização.
- Tags HTML ou valores de atributos incompatíveis entre a saída do servidor e do cliente
  (visível no aviso de hidratação do React DevTools).

## Como corrigir

Adie valores exclusivos do navegador para após a montagem, ou gere valores estáveis no lado do servidor
e passe-os como propriedades.

```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 Correção

```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.
```

## Teste

```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"
```