NG0500 Hydration Node Mismatch: o conserto de verdade não é ngSkipHydration

NG0500: Hydration Node Mismatch quer dizer que o DOM que o Angular montou no cliente não bateu com o HTML que o seu servidor mandou. O conserto popular, o ngSkipHydration, silencia o erro jogando a hydration fora naquele trecho. A doc chama isso de last resort, e trata o componente que quebra como um bug. O NG0500 é um diagnóstico: algo alterou o DOM entre o servidor e o cliente, e quase sempre é um item de uma lista curta.

O erro, e a família

A mensagem abre com NG0500: During hydration Angular expected .... A descrição oficial é precisa: durante a hydration o Angular esperava a estrutura de DOM que ele renderizou e anotou no servidor, mas no cliente a árvore de DOM era diferente. A hydration reaproveita o DOM renderizado no servidor em vez de recriar, então qualquer divergência entre as duas árvores trava o processo.

O NG0500 é o título de uma família, e você pode ter colado um vizinho. NG0501 é siblings faltando, NG0502 um nó faltando, NG0503 projeção de nós de DOM não suportada, NG0504 uma flag de skip-hydration num nó inválido, NG0505 sem info de hydration na resposta do servidor, NG0506 uma aplicação que segue instável, e NG0507 HTML que foi alterado depois do server-side rendering. Todos levam à mesma ideia: o DOM do cliente não é o que o servidor prometeu.

O que de fato causa

As causas são uma lista curta, e a maioria não é sutil depois que você conhece. A sorrateira é HTML inválido. O browser conserta calado uma marcação malformada, então o servidor manda uma árvore e o browser interpreta outra, e a hydration vê um mismatch que você não causou de propósito.

As outras: código que mexe no DOM direto (APIs nativas, innerHTML, outerHTML, appendChild, mover nós), bibliotecas de terceiro que renderizam no DOM (a doc cita os gráficos do D3), e o preserveWhitespaces setado diferente entre o build do servidor e o do cliente.

CausaPor que a hydration quebraDireção do fix
Aninhamento de HTML inválidoO browser normaliza; a árvore do cliente difere da do servidorConserte a marcação pra as duas darem a mesma árvore
DOM manipulado diretoinnerHTML, appendChild, mover nós muda a árvoreUse binding do Angular ou Renderer2
Biblioteca de DOM de terceiro (D3, etc.)Ela muta o DOM durante e depois do SSRRode só no cliente, depois da hydration
preserveWhitespaces inconsistenteOs builds de servidor e cliente emitem whitespace diferenteAlinhe o setting nos dois builds
A armadilha do aninhamento inválido (o browser reescreve)html
<!-- Quebra a hydration: o browser tira o <div> de dentro do <p>,
     então a árvore do cliente não bate mais com o HTML do servidor -->
<p>{{ summary }} <div class="badge">{{ status }}</div></p>

<!-- Válido: servidor e cliente interpretam a mesma árvore -->
<p>{{ summary }} <span class="badge">{{ status }}</span></p>

ngSkipHydration é torniquete, não cura

Busque NG0500 e a resposta do topo é ngSkipHydration. Funciona, e é esse o problema. O atributo força o Angular a pular a hydration do componente inteiro e dos filhos, então o erro some porque a hydration some naquele trecho. Coloque na raiz e você desligou a hydration do app inteiro, mas seguiu pagando o custo do SSR.

A doc é direta sobre o papel dele: é um last resort, e um componente que quebra a hydration deve ser tratado como bug a consertar. Então, se você for usar, dê escopo no host mais estreito, e trate como o playbook de SSR e hydration trata: todo ngSkipHydration ganha um dono, um motivo e um plano de remoção, ou vira cicatriz permanente que ninguém tem coragem de mexer.

Os fixes de verdade, por causa

Aninhamento inválido: conserte a marcação pra o HTML do servidor e o DOM interpretado pelo browser serem a mesma árvore. Um <div> dentro de um <p>, um <a> dentro de um <a>, uma <table> sem <tbody>, esses são os suspeitos de sempre.

DOM manipulado direto: leve pro lado das APIs do Angular, binding, Renderer2, o template, pra o framework ser dono da estrutura nos dois lados. Quando você precisa mesmo de uma biblioteca de DOM que pinta depois que a página existe, rode só no cliente com afterNextRender, que dispara depois da hydration e nunca no servidor, então não sobra nada pra hydration estranhar.

Trabalho de DOM só no cliente, depois da hydrationts
// Quebra a hydration: a lib de gráfico muta o DOM no servidor e no
// cliente, e as duas árvores não batem
ngOnInit() { this.renderChart(this.host.nativeElement); }

// Resolvido: rode só no cliente, depois que a hydration termina
private readonly host = inject(ElementRef);
constructor() {
  afterNextRender(() => this.renderChart(this.host.nativeElement));
}

Quando só quebra em produção

Alguns NG0500 passam local e falham só depois do deploy. A regra que a doc declara é que o HTML produzido pelo SSR não pode ser alterado entre o servidor e o cliente. Qualquer coisa no meio que reescreve a resposta quebra isso: um proxy ou CDN que minifica HTML, um edge worker que injeta uma tag, uma camada de segurança que reordena atributos. O servidor renderizou uma árvore, o browser recebeu outra, e o NG0507 é o código explícito pra HTML alterado depois do rendering.

Então, quando o componente está limpo mas o erro é real, olhe o caminho entre o seu servidor e o browser antes de pegar o ngSkipHydration. O fix é parar a reescrita, não pular a hydration de um componente que nunca foi o problema.

Artefato para reaproveitar

Depurando o mismatch de hydration NG0500

  • Leia qual nó a mensagem nomeia; é ali que a árvore do servidor e a do cliente divergiram.
  • Olhe o template atrás de aninhamento de HTML inválido (div em p, a em a, table sem tbody); o browser reescreve.
  • Ache o DOM manipulado direto (innerHTML, appendChild, APIs nativas) e leve pra binding do Angular ou Renderer2.
  • Pra biblioteca de DOM de terceiro, rode só no cliente com afterNextRender pra ela nunca rodar no SSR.
  • Se só falha em produção, cheque proxy ou CDN alterando o HTML no caminho (NG0507).
  • Use ngSkipHydration só como last resort, com escopo no host mais estreito, com dono e plano de remoção.

Perguntas frequentes

O que quer dizer o NG0500 Hydration Node Mismatch?

Na hydration, o Angular reaproveita o DOM que o seu servidor renderizou. O NG0500 quer dizer que a árvore de DOM no cliente ficou diferente da que o servidor produziu, então o Angular não consegue casar as duas. Algo mudou a estrutura entre o servidor e o cliente.

É só adicionar o ngSkipHydration?

Só como last resort. A doc diz que ele força o Angular a pular a hydration do componente e dos filhos, então você mantém o custo do SSR mas perde a hydration naquele trecho, e chama de bug um componente que quebra a hydration. Se usar, dê escopo estreito e um plano de remoção.

Por que o NG0500 acontece com template que parece válido?

O aninhamento de HTML inválido é a surpresa comum: um <div> dentro de um <p>, um <a> dentro de um <a>, ou uma <table> sem <tbody>. O browser reescreve a marcação calado, então o DOM interpretado não bate mais com o HTML do servidor.

Funciona local mas o NG0500 estoura em produção. Por quê?

O HTML do SSR não pode ser alterado entre o servidor e o cliente. Um proxy ou CDN que minifica HTML, ou uma camada de edge que injeta ou reordena marcação, muda a árvore que o browser recebe. O NG0507 é o código explícito pra HTML alterado depois do rendering.

Fontes consultadas

Modern Angular Playbook

Este artigo é um play.

O Playbook do Angular Moderno reúne o diagnóstico, a matriz de adoção, onze jogadas e o plano de 30 dias. Grátis, em inglês e português.

Você recebe os dois PDFs por email, pela lista do Dojo IA.

Abrir playbook →

André Ramosdisponível para vagas remotas, UTC−3Entre em contato →

Leia também

Guia · 10 min

SSR, hydration e @defer sem chute

Um guia prático de renderização no Angular com server routes, configuração de hydration, bordas com @defer, incremental hydration e proteção para DOM browser-only.

Ler artigo →

Erro · 8 min

NG0100 ExpressionChangedAfterItHasBeenCheckedError: por que ele aparece no Angular zoneless

Por que o NG0100 ExpressionChangedAfterItHasBeenCheckedError aparece depois de ir pra zoneless ou Angular 22, o que ele significa agora, a armadilha do fixture.detectChanges() no teste, e os fixes conforme de onde veio a mudança.

Ler artigo →

Guia · 10 min

O código que quebra quando Angular fica zoneless

Um guia técnico para revisar timers, subscriptions, forms, callbacks de terceiros e testes antes de um spike zoneless no Angular.

Ler artigo →