Back

Next.js Erro de Hidratação: O Guia Definitivo para nunca mais sofrer

Você abre o console e lá está ele. Aquele aviso vermelho chato.

Error: Text content does not match server-rendered HTML.
Warning: Prop className did not match. Server: "bg-blue-500" Client: "bg-red-500"

Quem trabalha com Next.js (ou qualquer framework com SSR) sabe que esse é o "batismo de fogo".
Muitas vezes a gente ignora porque "a tela tá funcionando". Mas cuidado: Erro de Hidratação não é só logs sujos. Significa que o React teve que jogar fora o trabalho do servidor e refazer tudo na unha (perda de performance), ou pior: seus botões podem simplesmente parar de responder.

Hoje vamos descer no nível do metal e entender: por que a Hidratação falha? E como mitigar isso de forma profissional, sem gambiarras.

O que é essa tal de Hidratação?

No modelo antigo (SPA/CRA), o HTML vinha vazio (<div id="root"></div>) e o JS construía a casa toda.
No Next.js (SSR), o servidor já manda a casa pronta (HTML com conteúdo).

  1. Server: Gera <h1>Olá Mundo</h1>.
  2. Browser: Recebe e pinta na tela (FCP rápido).
  3. Hidratação: O React acorda no navegador e diz "Beleza, deixa eu assumir o controle desse HTML". Ele tenta encaixar seus componentes no HTML existente.

O Problema:
Se o React (Browser) acha que deveria ser <h1>Olá Universo</h1>, mas o HTML (Server) diz <h1>Olá Mundo</h1>, dá ruim. O React entra em pânico e diz: "Não posso confiar nisso aqui".

Os 4 Causadores do Caos

99% das vezes é um desses aqui:

1. O Timezone Traiçoeiro (Timestamps)

Clássico. O servidor tá na AWS (UTC), você tá no Brasil (GMT-3).

export default function Footer() { // 💣 Erro na certa return <footer>Gerado às: {new Date().toLocaleTimeString()}</footer>; }
  • Server: "10:00:00"
  • Client: "07:00:00"

O HTML não bate. Erro. Isso vale pra Math.random() também.

2. HTML Inválido (O famoso div dentro do p)

O navegador é "mãe", ele corrige seus erros. O React é juiz.

NUNCA faça isso:

// ❌ Proibido <p> Olá <div>Mundo</div> </p>

A spec do HTML diz que <p> não pode ter <div> dentro. O navegador fecha o <p> automaticamente. O React (Virtual DOM) não sabe disso e continua procurando a div dentro do p. Mismatch.

Solução: Use <div> ou <span>.

3. Extensões de Browser (O inimigo invisível)

Seu código tá lindo, mas o erro persiste. Verifique suas extensões.
Grammarly, Tradutores, Dark Reader... eles injetam CSS/HTML na página antes do React carregar. O React vê aqueles elementos intrusos e não entende nada.

Teste na Aba Anônima. Se sumir, a culpa não é sua.

4. Acessar window no render

export default function Navbar() { // No servidor (Node.js) não tem window! const isMobile = window.innerWidth < 768; return <nav>{isMobile ? 'Menu Mobile' : 'Menu Desktop'}</nav>; }

Muitos tentam "burlar" com typeof window !== 'undefined'.
Isso causa o erro: o servidor renderiza o Desktop (porque window é undefined), mas seu celular renderiza o Mobile. HTMLs diferentes = Erro.

Como resolver de verdade?

1. Two-Pass Rendering (O jeito useEffect)

Se o dado varia entre server e client (tipo localStorage ou data), force o React a esperar.

// hooks/useIsMounted.ts import { useState, useEffect } from 'react'; export function useIsMounted() { const [mounted, setMounted] = useState(false); useEffect(() => { setMounted(true); }, []); return mounted; } function Relogio() { const isMounted = useIsMounted(); // 1. Server e Client inicial: Renderiza NADA (consistente) if (!isMounted) return null; // 2. Só depois de montar no browser, mostra a hora return <span>{new Date().toLocaleTimeString()}</span>; }

2. suppressHydrationWarning (A flag da paz)

Se for só um texto de data que não afeta o layout, você pode pedir pro React ignorar.

<span suppressHydrationWarning> {new Date().toLocaleTimeString()} </span>

Atenção: Só funciona em atributos de texto/conteúdo. Não resolve estrutura de div errada.

3. Disable SSR com dynamic (Next.js)

Se uma lib (tipo Leaflet Maps) usa window loucamente, remova ela do SSR.

import dynamic from 'next/dynamic'; const Mapa = dynamic(() => import('./Mapa'), { ssr: false, // Mágica aqui loading: () => <p>Carregando...</p>, });

O servidor manda o loading, e o cliente assume depois. Zero stress.

Conclusão

Viu esse erro?

  1. Cheque aninhamento de tags (p > div).
  2. Isole chamadas de window no useEffect.
  3. Desative extensões.

Hidratação é o preço da performance do SSR. Com esses padrões, você tira de letra. 🚀

Next.jsReactDebuggingWeb DevelopmentHydration