Checklist de incompatibilidade de hidratação no Next.js para UIs geradas por IA
Use este checklist de incompatibilidade de hidratação no Next.js para identificar rápido diferenças servidor vs cliente: datas, aleatoriedade, acesso ao window e renders instáveis.

O que é uma incompatibilidade de hidratação (em termos simples)
Com renderização no servidor, o Next.js envia HTML primeiro para que a página apareça rápido. Depois o React roda no navegador e “hidrata” esse HTML anexando handlers e renderizando a mesma UI novamente com os mesmos inputs.
Uma incompatibilidade de hidratação acontece quando o HTML vindo do servidor não bate com o que o React produz na primeira renderização no cliente. O React emite um aviso porque não pode assumir com segurança que o DOM é consistente.
Na prática, isso pode aparecer como um aviso no console, um rápido piscar quando o React substitui partes da página, botões que não respondem por um momento, ou texto que muda logo após o carregamento. Às vezes é sutil. Às vezes quebra formulários, UI de autenticação e qualquer coisa que dependa de um DOM estável.
Código gerado por IA tende a bater mais nisso porque costuma misturar valores só do navegador na renderização sem proteção. Exemplos típicos: ler window durante a renderização, formatar datas de modo diferente no servidor e no cliente, ou usar aleatoriedade para escolher uma cor, ID ou valor padrão.
Regra simples: se o servidor e o navegador podem produzir respostas diferentes, não use esse valor diretamente na renderização.
O que você geralmente pode ignorar vs o que deve consertar:
- Geralmente baixo risco: uma pequena diferença de
classNameque não afeta layout ou entrada do usuário. - Conserte rápido: qualquer coisa que cause pulo de conteúdo, reset de inputs, inversão de estado de autenticação ou elementos que desaparecem.
- Sempre conserte: qualquer coisa ligada à identidade ou segurança (por exemplo, mostrar o estado de usuário errado).
Triagem rápida: reproduza e isole a incompatibilidade
Um aviso de hidratação é específico: o servidor enviou HTML, então o navegador tentou anexar o React a ele, e a primeira renderização do cliente não bateu com o que já estava na página. Antes de caçar, confirme que não é de fato uma falha de fetch (dados vazios), um redirect, ou um shift de layout que apenas parece com problema de hidratação.
Reproduza em um build de produção. O modo dev pode adicionar renders e avisos extras que atrapalham achar o gatilho.
Fluxo rápido de triagem:
- Rode um build de produção, inicie o app localmente e atualize a página que mostra o aviso.
- Teste tanto um refresh completo (first load) quanto uma navegação cliente-side para a mesma rota.
- Quebra apenas no refresh: suspeite de diferenças na saída SSR.
- Quebra apenas após navegação: suspeite de estado do cliente ou efeitos.
- Leia a dica exata no aviso do React (frequentemente um elemento específico, um nó de texto ou um atributo).
- Remova temporariamente blocos da página até o aviso desaparecer, então vá adicionando de volta até encontrar o menor componente que ainda falha.
- Quando tiver o menor componente com falha, compare o que ele renderiza no servidor vs no cliente. Uma diferença de um único caractere já é suficiente.
Enquanto estiver investigando, anote:
- Rota e passos exatos (refresh vs navegação)
- O elemento ou texto que o React nomeia
- Se a incompatibilidade é de conteúdo (texto), atributos (
class,style) ou estrutura (wrapper extra) - Se os dados estão presentes no primeiro paint ou aparecem depois
Um padrão comum em widgets de IA: um cabeçalho “Welcome, Sam” é renderizado no servidor como “Welcome” (sem usuário ainda), então o cliente preenche o nome instantaneamente a partir do localStorage. Isso é um mismatch, e “refresh vs navigation” costuma deixar isso óbvio.
Checagens rápidas de 10 minutos
As vitórias mais rápidas vêm de encontrar qualquer coisa que possa renderizar diferente no servidor do que no navegador. Você não está consertando tudo ainda — está identificando a entrada que muda entre o HTML do servidor e a primeira renderização do cliente.
Varredura rápida que pega a maioria dos bugs gerados por IA
Abra o componente nomeado no aviso (ou a página) e procure os suspeitos habituais:
Date, Math.random, window, document, navigator, localStorage, sessionStorage.
Rotina simples:
- Reduza a página ao menor componente que ainda dispara o aviso.
- Procure valores dinâmicos usados em texto JSX, atributos ou props (hora, números aleatórios, checagens de viewport).
- Verifique render condicional baseado em viewport, user agent ou media queries em JavaScript (não em CSS).
- Procure
keyinstáveis, IDs gerados ou nomes de classe que mudam entre execuções. - Hardcode o valor suspeito (por exemplo, uma string de data fixa) para confirmar se o aviso desaparece.
Escolha a correção mais segura
A maioria das correções se encaixa em alguns padrões:
- Tornar a renderização determinística: calcule o valor no servidor e passe como prop.
- Atrasar o valor até depois da montagem: renderize um placeholder e então ajuste o estado em
useEffect. - Proteger APIs só do navegador: verifique
typeof window !== "undefined"antes de lê-las. - Mover um widget para client-only quando ele realmente depende do estado do navegador.
Checklist: datas, fusos horários e formatação de locale
Datas são uma das principais causas de problemas de hidratação porque o servidor e o navegador podem discordar sobre “agora”, fuso horário ou locale padrão. Se o texto renderizado difere mesmo ligeiramente, o React reclama.
Checagens rápidas
Procure por UI que transforma “agora” em texto durante a renderização:
new Date()ouDate.now()dentro do render do componenteIntl.DateTimeFormat(...)semtimeZoneelocalefixos- Tempo relativo como “agora mesmo” calculado durante SSR
- Timers ou contadores que dependem do segundo atual
- Servidor usando UTC enquanto o navegador usa fuso local
Um padrão comum é:
"Last updated: {new Date().toLocaleString()}"
Isso quase sempre vai diferir entre servidor e cliente.
Padrões de correção que mantêm a página estável
Escolha a abordagem que corresponde ao que os usuários realmente precisam:
- Passe o timestamp como dado e renderize esse valor exato.
- Formate com um fuso horário explícito (frequentemente
UTC) e locale escolhido. - Se precisar de tempo relativo, renderize um placeholder estável no servidor e calcule após a montagem.
- Para relógios/contadores ao vivo, renderize um valor inicial fornecido pelo servidor e então comece a atualizar num efeito.
Checklist: aleatoriedade e renderização não determinística
Outra causa comum é simples: o servidor renderiza uma versão, e o navegador renderiza outra porque o código “aleatório” rodou de novo.
Verifique primeiro:
Math.random()usado para IDs, cores, escolher um card, avatares ou variantes- Ordenação ou embaralhamento dentro do render (incluindo
items.sort(...)que muta) - Keys criadas na hora (
key={Math.random()},key={Date.now()}) - Geradores de conteúdo de amostra que retornam texto diferente em cada execução
Torne a primeira renderização determinística:
- Pré-compute no servidor e passe como prop.
- Use chaves estáveis a partir de IDs reais (ou índices apenas para listas realmente estáticas).
- Mova a aleatoriedade para depois da montagem (
useEffect) para que só afete atualizações no cliente.
Exemplo: um widget “Featured templates” embaralha templates durante a renderização e usa índices embaralhados como keys. O servidor renderiza ordem A, o cliente ordem B, e a hidratação falha. Pré-embaralhe uma vez (lado servidor) ou inicialize estado a partir de uma lista fornecida pelo servidor, então use o ID estável do template como key.
Checklist: APIs só do navegador e checagens de ambiente
Mismatches de hidratação frequentemente ocorrem quando o servidor renderiza uma versão e o navegador imediatamente renderiza outra porque o código leu valores só do navegador cedo demais.
O que procurar
Procure por APIs do navegador usadas durante a renderização (incluindo helpers chamados no render): window, document, localStorage, sessionStorage, navigator.
Também fique atento a UI que muda estrutura com base no tamanho da tela. Se você ler window.innerWidth ou matchMedia() para decidir qual árvore de componentes renderizar (em vez de apenas estilos), o servidor está chutando, e vai errar para alguns usuários.
Padrões de correção mais seguros
Mantenha a primeira renderização determinística, então atualize após a montagem:
- Proteja acesso ao navegador:
if (typeof window !== "undefined") { ... } - Mova leituras do navegador para
useEffect(ouuseLayoutEffectapenas quando realmente necessário) - Renderize um placeholder estável no servidor e substitua no cliente
- Prefira CSS para mudanças responsivas em vez de render condicional
- Se a feature toda depende do estado do navegador, torne-a client-only e mantenha a saída do servidor mínima
Checklist: carregamento de dados e inconsistências de estado de autenticação
Essas incompatibilidades acontecem quando o servidor renderiza uma “primeira vista”, mas o navegador imediatamente a substitui por outra com base em dados cacheados ou estado de autenticação.
Gatilhos comuns:
- Servidor renderiza “0 itens”, e o cliente hidrata com itens em cache no localStorage ou IndexedDB.
- Servidor pensa que o usuário está deslogado, mas o cliente lê um token e mostra UI logada.
- Feature flags avaliadas de forma diferente (padrões do servidor vs preferências armazenadas).
Padrões de correção que mantêm o primeiro paint consistente:
- Use um shell de carregamento consistente (mesma marcação no servidor e na primeira renderização do cliente), e então troque pelos dados reais.
- Passe dados iniciais e estado de autenticação do servidor para o cliente para que ele comece no mesmo estado.
- Atrase leitura de caches do navegador até depois da hidratação e renderize um placeholder até lá.
- Mantenha defaults de flags idênticos entre servidor e cliente e então aplique overrides do usuário após a hidratação.
Checklist: estilização, layout e renderização responsiva
Alguns problemas de hidratação são “mesmos dados, estrutura diferente”. O servidor renderiza uma forma do DOM, o navegador renderiza outra depois que sabe tamanho da tela, fontes ou medidas.
Lógica responsiva que muda o DOM
Se sua UI renderiza árvores de componente diferentes por breakpoint (por exemplo, “menu mobile” vs “abas desktop”), o servidor precisa adivinhar.
Prefira renderizar o mesmo DOM em ambos os lados e então trocar a apresentação com CSS. Se for necessário mudar a marcação, garanta que só mude após a montagem.
CSS-in-JS e ordem de nomes de classe
Configuração de SSR mal feita em algumas bibliotecas de estilos pode produzir nomes de classe ou ordens de inserção diferentes entre servidor e cliente. Se o aviso cita diferenças de className ou você vê um flash de estilo, confirme que está usando o setup SSR documentado para sua biblioteca de estilos e evite gerar estilos a partir de valores não determinísticos.
Medidas de layout e carregamento de fontes
Medições feitas em tempo de render como getBoundingClientRect() não podem bater no servidor. Meça em useEffect, renderize um placeholder estável primeiro e aplique mudanças dependentes de layout após a montagem.
Passo a passo: maneiras mais seguras de estabilizar páginas
O objetivo é simples: o primeiro HTML que o servidor envia deve coincidir com o que o navegador renderiza antes de qualquer efeito rodar.
Sequência confiável para estabilizar:
-
Identifique o que precisa bater. Foque no nó exato que o React aponta.
-
Torne a saída do servidor determinística. Pré-compute valores no servidor e passe como props. Evite chamar
new Date()durante o render. -
Adie lógica só do navegador. Qualquer coisa que precise de
window,document,localStorage, tamanho de tela ou preferências do usuário deve começar com marcação estável e atualizar emuseEffect, ou viver inteiramente em um Client Component. -
Isole widgets arriscados. Se um componente depende de APIs do navegador ou de valores não determinísticos, carregue-o somente no cliente para que o resto da página permaneça estável:
import dynamic from "next/dynamic";
const ClientOnlyWidget = dynamic(() => import("./Widget"), { ssr: false });
- Use
suppressHydrationWarningcomo último recurso. Limite-o a textos pequenos e seguros onde uma incompatibilidade única é aceitável. Não o use em UI interativa ou com conteúdo condicionado por autenticação.
Erros comuns que fazem o problema voltar
Avisos de hidratação muitas vezes somem depois de um patch, então voltam quando alguém adiciona um novo badge, banner ou checagem de autenticação.
“As correções” que causam dor a longo prazo:
- Desabilitar SSR para uma página inteira quando só um widget pequeno é instável.
- Renderizar “agora” diretamente no JSX (timestamps, rótulos “agora mesmo”).
- Criar keys a partir de valores aleatórios ou tempo.
- Usar
useLayoutEffectpara mudanças só do navegador sem um plano client-only. - Tratar supressão como solução principal.
Se a marcação muda com base em isLoggedIn antes do cliente saber da sessão, renderize primeiro um shell neutro e então troque quando a autenticação for confirmada.
Exemplo realista: um widget gerado por IA que quebra hidratação
Um cartão comum de dashboard mostra “Updated 12 seconds ago” e computa isso usando Date.now(), o locale do usuário, e às vezes um fuso preferido vindo do localStorage.
Essa é uma receita perfeita para mismatch: o servidor renderiza uma string (hora do servidor, locale do servidor, sem localStorage), o navegador renderiza outra (hora do cliente, locale do cliente, preferências armazenadas).
Aqui vai uma reescrita mais segura que mantém a primeira renderização estável e depois atualiza após a hidratação:
function UpdatedLabel({ updatedAt, initialNow, locale }: {
updatedAt: number
initialNow: number
locale: string
}) {
const [text, setText] = React.useState(() =>
formatRelative(initialNow, updatedAt, locale)
)
React.useEffect(() => {
const id = window.setInterval(() => {
setText(formatRelative(Date.now(), updatedAt, locale))
}, 1000)
return () => window.clearInterval(id)
}, [updatedAt, locale])
return <span>{text}</span>
}
Ideia-chave: servidor e cliente compartilham o mesmo initialNow e locale para a primeira pintura, então a marcação bate. Só então o cliente começa a atualizar.
Para validar, teste as situações que costumam causar divergência:
- Build de produção (não modo dev)
- Refresh completo com cache desabilitado
- Fuso horário ou locale diferente
- Sessão anônima (sem preferências armazenadas)
Verificação final e quando pedir ajuda
Após uma mudança, teste como se estivesse tentando quebrar:
- Refresh completo (não só navegação cliente-side)
- Janela anônima (sem dados em cache, menos extensões)
- Throttling de rede lento
- Navegador com idioma ou fuso horário diferente
Também ajuda manter uma nota curta de “regras SSR” perto do código de UI que tende a ser re-gerado: nada de window durante o render, nada de Math.random() no markup, formatação de data sem fuso horário explícito, e nada de UI condicionada por autenticação até o estado de auth estar conhecido.
Se ainda vir mismatches depois das correções óbvias, a base de código geralmente está lutando com você. Isso é comum em protótipos gerados por ferramentas como Lovable, Bolt, v0, Cursor ou Replit. Times às vezes contratam FixMyMess (fixmymess.ai) para uma auditoria rápida que localize a divergência exata servidor/cliente e repare as partes instáveis sem desligar SSR para tudo.
Perguntas Frequentes
O que é uma hydration mismatch no Next.js, em linguagem simples?
Uma incompatibilidade de hidratação acontece quando o HTML que o Next.js envia do servidor não bate com o que o React renderiza no cliente durante a primeira renderização. O React emite um aviso porque não consegue anexar handlers com segurança a um DOM que não foi criado por ele.
Você normalmente vai notar um aviso no console, um breve piscar ou a interface mudando logo após o carregamento.
Como saber se o problema está relacionado ao SSR ou ao estado do cliente?
Comece reproduzindo em um build de produção, não em modo dev. Depois compare um refresh completo (hard refresh) com uma navegação cliente-side para a mesma rota.
Se só quebra no refresh, geralmente é diferença entre SSR e a primeira renderização no cliente. Se quebra principalmente após navegação, normalmente é estado do cliente, efeitos ou dados em cache.
Quais são as coisas mais rápidas para procurar quando vejo um aviso de hidratação?
Procure por qualquer coisa que possa produzir saída diferente entre servidor e navegador durante a renderização: new Date(), Date.now(), Math.random(), formatação de locale, window, document, navigator, localStorage e sessionStorage.
Se esses valores afetam texto, atributos ou quais elementos são renderizados, há um forte candidato à incompatibilidade.
Por que datas e fusos horários causam tantos problemas de hidratação?
Porque o servidor e o navegador podem discordar sobre “agora”, fuso horário e locale padrão. Mesmo uma diferença de um caractere numa timestamp formatada já é suficiente para disparar o aviso.
O padrão mais seguro é renderizar uma timestamp estável vinda dos dados (ou um valor conhecido fornecido pelo servidor) e só calcular “tempo relativo” após o componente montar.
Por que usar Math.random() no JSX quebra a hidratação?
Porque ele roda duas vezes: uma no servidor e outra no navegador. Se você chama Math.random() durante a renderização para escolher um ID, cor, variante ou key, o servidor e o cliente provavelmente vão escolher resultados diferentes.
Torne a primeira renderização determinística usando IDs estáveis dos seus dados, ou mova a aleatoriedade para uma atualização após a montagem.
Qual é a forma correta de usar window ou localStorage sem causar mismatch?
Ler APIs só do navegador durante a renderização faz com que o cliente produza saída diferente do servidor, porque o servidor não consegue ler esses valores. Um exemplo comum é mostrar UI “logado” com base em um token de localStorage.
Uma correção prática é renderizar um shell neutro e estável no servidor e popular o estado derivado do navegador em useEffect depois da hidratação.
Como evitar incompatibilidades com estado de autenticação (UI logado vs deslogado)?
Se o servidor renderiza “deslogado” e o cliente imediatamente renderiza “logado” após ler um token, o React vê marcação diferente. Isso também pode causar reset de inputs ou botões que não funcionam momentaneamente.
A abordagem limpa é fazer servidor e primeira renderização do cliente concordarem, passando o estado inicial da sessão do servidor quando possível, ou renderizando um estado de carregamento/skeleton consistente até a autenticação ser confirmada.
A lógica de UI responsiva pode causar mismatches de hidratação?
Sim. Se você usa window.innerWidth ou matchMedia() durante a renderização para escolher entre duas árvores de componentes diferentes, o servidor está basicamente adivinhando o tamanho da tela do usuário.
Prefira renderizar o mesmo DOM em ambos os lados e usar CSS para alterar a apresentação. Se precisar mudar a marcação, faça isso depois da montagem para que a primeira renderização permaneça estável.
Quando é aceitável usar suppressHydrationWarning, e quando é arriscado?
Use-o apenas para textos pequenos e não interativos onde uma diferença pontual é aceitável. É basicamente dizer ao React “não alerte aqui”, não tornar a UI de fato consistente.
Evite em campos de formulários, UI condicionada por autenticação ou qualquer coisa que afete identidade, permissões ou ações do usuário.
E se eu não conseguir encontrar o mismatch em uma base Next.js gerada por IA?
Se você removeu as causas óbvias e o aviso continua mudando de lugar, a base de código pode ter múltiplas fontes de divergência servidor/cliente. Isso é comum em protótipos gerados por IA onde leituras só do navegador, chaves aleatórias e formatação de datas acabam misturadas nas rotas de render.
FixMyMess pode rodar uma auditoria rápida para localizar o componente e o valor exatos que causam o mismatch e então reparar as partes instáveis sem desativar SSR para tudo, geralmente em 48–72 horas.