Como Corrigir Erros de Hidratação no Nuxt cover image

Como Corrigir Erros de Hidratação no Nuxt

Se você já colocou no ar uma aplicação Nuxt com renderização no servidor, provavelmente já viu no console um aviso sobre hidratação ou mismatch. A página pode parecer normal, mas o Vue está sinalizando algo importante: o DOM que ele esperava no cliente não corresponde ao HTML que o servidor enviou.

Hidratação é o momento em que o Vue assume o HTML estático renderizado no servidor e transforma tudo em uma aplicação reativa. Quando essa transição falha, surgem erros de hidratação. Eles não são apenas visuais. Podem causar flicker, interações quebradas, nós duplicados ou bugs sutis que só aparecem em produção, com rede e cache reais.

A boa notícia é que a maioria dos problemas de hidratação no Nuxt segue padrões previsíveis. Quando você aprende a ler o aviso e rastrear até o componente responsável, a correção costuma ser direta.

O Que É Hidratação no Nuxt

Com SSR ou SSG, o Nuxt renderiza seus componentes Vue em HTML no servidor. Esse HTML chega primeiro ao navegador, o que melhora a percepção de performance e o SEO. Depois que o JavaScript carrega, o Vue hidrata esse markup: percorre o DOM existente, anexa listeners e reconcilia o virtual DOM com o que já está na tela.

A hidratação assume que servidor e cliente produzem a mesma saída para a mesma árvore de componentes, com os mesmos dados. Se o cliente renderizar algo diferente — mesmo que seja um único nó de texto ou atributo — o Vue emite um aviso e pode descartar o HTML do servidor, re-renderizando no cliente. Esse é o mismatch.

No Nuxt 3 e Nuxt 4, isso vale para páginas, layouts e qualquer componente renderizado no SSR, a menos que você opte explicitamente por ClientOnly ou desative SSR em uma rota.

Causas Comuns em Projetos Reais

A maior parte dos mismatches não é mistério. Ela vem de código que se comporta de forma diferente dependendo da existência de window, document ou APIs exclusivas do navegador.

  • Formatação de data e hora. Renderizar new Date() ou Intl.DateTimeFormat no SSR gera valores que podem divergir por fuso horário, locale ou milissegundos até a hidratação no cliente.
  • Valores aleatórios e IDs. Chamar Math.random() ou gerar UUIDs durante o render cria saídas diferentes entre servidor e cliente.
  • Estado exclusivo do navegador. Ler localStorage, sessionStorage, cookies sem acesso seguro no SSR ou window.innerWidth no setup ou template altera o markup entre ambientes.
  • Renderização condicional por detecção de cliente. Padrões como if (process.client) em expressões de template ou computed que retornam HTML diferente em cada lado são causa frequente de mismatch.
  • Scripts e widgets de terceiros. Chat, mapas, anúncios e ferramentas de A/B testing costumam mutar o DOM antes ou durante a hidratação.
  • Aninhamento HTML inválido. Navegadores corrigem estruturas malformadas (por exemplo, <p> dentro de outro <p>). O renderer do servidor e o navegador podem corrigir de modos distintos, gerando diferença na contagem de nós.
  • Espaços em branco e nós de comentário. Menos comum no Nuxt atual, mas diretivas customizadas ou manipulação manual do DOM ainda podem introduzir nós de texto inesperados.

Identificar em qual categoria seu bug se encaixa reduz drasticamente o tempo de depuração.

Como Depurar Mismatches de Forma Eficiente

Comece em desenvolvimento com SSR habilitado. O modo dev do Nuxt exibe avisos de hidratação com rastreamento de componentes. Leia a mensagem completa: o Vue frequentemente indica se o conteúdo de texto, atributos ou quantidade de filhos divergiu.

Siga estes passos em ordem:

  • Reproduza com hard refresh. Desative extensões do navegador que injetam nós no DOM. Extensões sozinhas causam muitos falsos positivos no ambiente local.
  • Faça busca binária no layout. Remova seções temporariamente ou envolva componentes suspeitos em ClientOnly para confirmar qual deles dispara o aviso.
  • Inspecione a saída do SSR. Veja o código-fonte da página ou faça curl na URL para ver exatamente o que o servidor renderizou antes do JavaScript do cliente executar.
  • Compare render do servidor e do cliente. Em casos difíceis, registre um snapshot serializado no SSR com useRequestEvent ou hooks exclusivos do servidor e compare com a saída após o mount no cliente.
  • Ative avisos verbosos do Vue. Eles ajudam a identificar qual vnode falhou na reconciliação.

Anote o primeiro componente controlado por você na stack trace. Os avisos propagam para cima, mas a causa raiz quase sempre está em um componente folha que renderiza conteúdo não determinístico.

Correções Que Funcionam em Apps Nuxt em Produção

Torne o Render Determinístico no Servidor

A melhor correção é garantir que servidor e cliente produzam o mesmo HTML inicial. Passe valores estáveis do servidor sempre que possível.

Para datas, formate no servidor e envie uma string ISO como prop; formate no cliente dentro de onMounted apenas se precisar de atualização em tempo real. Para tempos relativos como “há 3 minutos”, renderize um placeholder estático no SSR e atualize após o mount.

Para IDs usados em atributos for ou relações ARIA, gere-os de forma segura no SSR. Um padrão comum é usar useId() no Vue 3.5+ ou derivar IDs de dados estáveis do servidor, em vez de valores aleatórios no render.

Adie UI Exclusiva do Cliente com ClientOnly

Quando um componente realmente não pode renderizar no servidor — gráficos, editores rich text, bibliotecas de mapas — envolva-o no componente ClientOnly do Nuxt. Forneça um slot #fallback com skeleton que preserve o layout aproximado e evite layout shift.

Não envolva páginas inteiras em ClientOnly sem motivo forte. Você perde os benefícios do SSR naquela subárvore. Use de forma cirúrgica nos widgets que dependem de APIs do navegador.

Isole Lógica do Navegador em onMounted

Mova leituras de localStorage, preferências de tema, geolocalização e media queries para onMounted ou useState com default no servidor. Inicialize estado reativo com valor seguro para SSR e sincronize com o navegador após a hidratação.

Abordagem prática para tema:

  • Default no servidor: light
  • Após mount no cliente: ler preferência salva e atualizar
  • Evite aplicar classes de tema no template raiz antes do mount, a menos que o servidor conheça a preferência via cookie

Se o tema precisa estar correto no first paint, armazene a preferência em cookie e leia durante o SSR com useRequestHeaders ou middleware de servidor para que ambos os ambientes concordem.

Use Composables Seguros para SSR e Primitivas do Nuxt

Prefira recursos nativos do Nuxt a checagens manuais de ambiente. useCookie sincroniza estado de cookie entre servidor e cliente. useState transporta valores obtidos no servidor sem recomputação. useAsyncData e useFetch garantem que dados usados em templates sejam resolvidos de forma consistente antes do render nos dois lados.

Substitua ramificações manuais com if (import.meta.client) em templates por composables que encapsulam comportamento seguro no SSR. Centralizar a lógica reduz regressões quando o time adiciona funcionalidades depois.

Corrija HTML Inválido e Estrutura de Componentes

Revise sua UI com olhar semântico: elementos interativos não devem aninhar de forma incorreta, tabelas precisam de estrutura adequada e componentes que renderizam fragments não devem quebrar expectativas do pai.

Ao usar v-html, lembre que servidor e cliente devem receber a mesma string sanitizada. Se a sanitização divergir entre ambientes, você terá mismatch e possíveis problemas de segurança.

Quando Desabilitar SSR É Aceitável

Para um dashboard autenticado, atrás de login, onde SEO não importa, routeRules: { ssr: false } ou definePageMeta({ ssr: false }) pode ser pragmático. Você abre mão do SSR naquela rota em troca de eliminar toda uma classe de problemas de hidratação.

Use com moderação. Páginas de marketing, blogs e vitrines de produto devem permanecer com SSR. Ferramentas internas e apps pesados no cliente são melhores candidatos.

Como Evitar Regressões ao Longo do Tempo

Adicione testes de integração que renderizem páginas com utilitários de SSR ou testes end-to-end com JavaScript habilitado, garantindo ausência de avisos de hidratação no console. No CI, falhe o build ao detectar novos avisos, se seu runner suportar captura de console.

Em code review, sinalize valores aleatórios, acesso direto a window e formatação de datas no setup de componentes. Defina convenções de time: código exclusivo do navegador vive em composables com defaults documentados para SSR.

Documente ilhas ClientOnly conhecidas na arquitetura para que refactors futuros não movam bibliotecas incompatíveis com SSR para layouts renderizados no servidor.

Um Modelo Mental Prático

Trate o SSR como um contrato: o servidor promete um snapshot específico de HTML, e o cliente deve aceitar esse snapshot literalmente no primeiro render. Tudo que violar o contrato — tempo, aleatoriedade, estado do navegador ou markup malformado — deve ir para data fetching consciente do servidor, atualizações pós-mount no cliente ou limites ClientOnly.

Seguindo esse modelo, erros de hidratação deixam de parecer bugs misteriosos do framework e passam a ser problemas previsíveis de integração, resolvíveis em minutos. Essa é a diferença entre lutar contra o Nuxt e entregar aplicações estáveis e rápidas com confiança.