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: PropclassNamedid 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).
- Server: Gera
<h1>Olá Mundo</h1>. - Browser: Recebe e pinta na tela (FCP rápido).
- 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?
- Cheque aninhamento de tags (
p > div). - Isole chamadas de
windownouseEffect. - Desative extensões.
Hidratação é o preço da performance do SSR. Com esses padrões, você tira de letra. 🚀