🌐 Detecting your location…

Como corrigir erro de falha de hidratação em Next.js: solução completa

⏱️6 min read  ·  1,139 words

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.

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.

MD Rafikul Islam

Written by

MD Rafikul Islam is a software developer and the editor of TechPulse. He writes about developer tooling, hardware, and the practical decisions that come up in day-to-day engineering work — which laptop to buy, which framework to commit to, why a build broke at 2am. He tests the tools he writes about and says plainly when something is not worth the money. Corrections and corrections requests are welcome at rony.yf25@gmail.com.

✍️ Leave a Comment

Your email address will not be published. Required fields are marked *

🌐 Read in:🇬🇧 English🇩🇪 Deutsch🇧🇷 Português🇸🇦 العربية🇮🇳 हिन्दी🇧🇩 বাংলা