O erroA hidratação falhou porque a UI inicial não corresponde ao que foi renderizado no servidor (ou “O conteúdo do texto não corresponde ao HTML renderizado pelo servidor”) em Next.js significa que o HTML que o servidor renderizou difere daquele que o React renderiza no cliente. Veja por que isso acontece e como corrigir todas as causas.
📋 Table of Contents
- O que é Hidratação
- Causa 1: Usando APIs somente para navegador durante a renderização
- Causa 2: Datas e horários
- Causa 3: Valores Aleatórios
- Causa 4: Aninhamento de HTML inválido
- Causa 5: Extensões do navegador modificando HTML
- Causa 6: Renderização Condicional Baseada no Estado do Cliente
- O padrão de componente somente cliente
- Perguntas Frequentes
- Conclusão
O que é Hidratação
Next.js renderiza sua página em HTML no servidor (SSR), envia-a ao navegador para exibição inicial rápida e, em seguida, o React a “hidrata” – anexando interatividade. A hidratação requer que o HTML renderizado pelo servidor e a primeira renderização do cliente correspondam exatamente. Se forem diferentes, o React gera um erro de hidratação porque não consegue reconciliar a incompatibilidade.
Causa 1: Usando APIs somente para navegador durante a renderização
// 🐛 window/localStorage don't exist on the server → mismatch
function Component() {
const theme = localStorage.getItem('theme'); // ❌ undefined on server
return <div className={theme}>...</div>;
}
// ✅ Access browser APIs only after mount (in useEffect)
function Component() {
const [theme, setTheme] = useState(null);
useEffect(() => {
setTheme(localStorage.getItem('theme')); // runs only on client
}, []);
return <div className={theme || 'default'}>...</div>;
}
Causa 2: Datas e horários
// 🐛 The server and client render at different times → mismatch
function Component() {
return <div>{new Date().toLocaleString()}</div>; // ❌ differs
}
// ✅ Render the date only on the client
function Component() {
const [date, setDate] = useState(null);
useEffect(() => { setDate(new Date().toLocaleString()); }, []);
return <div>{date ?? 'Loading...'}</div>;
}
Causa 3: Valores Aleatórios
// 🐛 Math.random() produces different values on server vs client
function Component() {
const id = Math.random(); // ❌ different each render
return <div id={id}>...</div>;
}
// ✅ Use React's useId for stable IDs, or generate in useEffect
import { useId } from 'react';
function Component() {
const id = useId(); // stable across server and client
return <div id={id}>...</div>;
}
Causa 4: Aninhamento de HTML inválido
// 🐛 Invalid nesting gets "corrected" by the browser, causing mismatch
<p>
<div>Content</div> {/* ❌ div inside p is invalid */}
</p>
// The browser moves the div out, but React's tree still has it nested
// ✅ Use valid HTML nesting
<div>
<div>Content</div> {/* valid */}
</div>
// Common culprits: div/p inside p, block elements inside inline elements,
// invalid table structure
Causa 5: Extensões do navegador modificando HTML
// Some browser extensions inject attributes/elements into your HTML
// before React hydrates, causing a mismatch (e.g., Grammarly, dark mode extensions)
// This often shows as a mismatch on the body or specific elements.
// You can suppress the warning on a specific element if needed:
<body suppressHydrationWarning>
{/* Use sparingly - only when the mismatch is expected/harmless */}
</body>
// But first verify it's an extension, not a real bug in your code.
Causa 6: Renderização Condicional Baseada no Estado do Cliente
// 🐛 Rendering differently based on something only known on the client
function Component() {
const isMobile = window.innerWidth < 768; // ❌ window undefined on server
return isMobile ? <Mobile /> : <Desktop />;
}
// ✅ Start with a consistent server render, adjust after mount
function Component() {
const [isMobile, setIsMobile] = useState(false); // consistent default
useEffect(() => {
setIsMobile(window.innerWidth < 768);
const onResize = () => setIsMobile(window.innerWidth < 768);
window.addEventListener('resize', onResize);
return () => window.removeEventListener('resize', onResize);
}, []);
return isMobile ? <Mobile /> : <Desktop />;
}
O padrão de componente somente cliente
// For components that genuinely can't be server-rendered,
// use dynamic import with ssr: false
import dynamic from 'next/dynamic';
const ClientOnlyChart = dynamic(() => import('./Chart'), {
ssr: false, // skip server rendering entirely
loading: () => <div>Loading chart...</div>,
});
// The component renders only on the client - no hydration mismatch possible
Perguntas Frequentes
P: O que causa erros de hidratação com mais frequência?
R: Uso de APIs somente de navegador (janela, localStorage) durante a renderização, renderização de datas/horas ou valores aleatórios que diferem entre servidor e cliente e aninhamento de HTML inválido. O ponto comum: algo é renderizado de maneira diferente no servidor e no cliente. Mova a lógica específica do cliente para useEffect.
P: Como uso o localStorage sem erros de hidratação?
R: Não acesse durante a renderização (é indefinido no servidor). Leia-o em useEffect (que roda apenas no cliente) e armazene o valor no estado. Renderize inicialmente um padrão consistente e atualize após a montagem. Isso mantém a correspondência das renderizações do servidor e do cliente.
P: Meu erro de hidratação é causado por uma extensão do navegador. O que eu faço?
R: Extensões como Grammarly injetam atributos antes da hidratação. Verifique se é a extensão (teste no modo anônimo com as extensões desativadas). Se for inofensivo, você pode adicionarsuppressHydrationWarning ao elemento afetado – mas use-o com moderação e apenas para modificações externas inofensivas confirmadas, não para esconder bugs reais.
P: Quando devo usar a importação dinâmica com ssr: false?
R: Para componentes que realmente não podem ou não deveriam ser renderizados pelo servidor — aqueles que dependem fortemente de APIs de navegador, bibliotecas de terceiros somente para clientes ou que não se beneficiam do SSR. Ele ignora totalmente a renderização do servidor, eliminando incompatibilidades de hidratação para esse componente, sem nenhum benefício de SSR para ele.
P: Por que funciona no desenvolvimento, mas às vezes o erro aparece?
R: As incompatibilidades de hidratação podem ser intermitentes quando dependem de tempo (datas), aleatoriedade ou fatores externos (extensões). Eles são bugs reais mesmo quando intermitentes. Reproduza de forma confiável identificando o que difere entre a renderização do servidor e do cliente — geralmente APIs do navegador, horário ou valores aleatórios.
Conclusão
Erros de hidratação do Next.js significam que o HTML renderizado pelo servidor não corresponde à primeira renderização do cliente. As causas são consistentes:APIs somente para navegador (janela, localStorage) usadas durante a renderização, datas/horas ou valores aleatórios diferentes, aninhamento de HTML inválido e renderização condicional específica do cliente. O padrão de correção é o mesmo: mova a lógica específica do cliente parauseEffect (que é executado apenas no cliente), renderiza inicialmente um padrão consistente e atualiza após a montagem. UsaruseId para IDs estáveis, aninhamento HTML válido edynamic(..., ssr: false) para componentes genuinamente somente cliente. Depois que as renderizações do servidor e do cliente produzirem HTML correspondente, a hidratação será bem-sucedida. O modelo mental principal: qualquer renderização deve ser idêntica no servidor e no cliente para a renderização inicial – qualquer coisa específica do cliente pertence a useEffect.
🔗 Share this article
✍️ Leave a Comment