Como Resolver o Erro “Hydration failed because the initial UI does not match” no Next.js [Guia Atualizado 2026]

スポンサーリンク

Como Resolver o Erro “Hydration failed because the initial UI does not match” no Next.js [Guia Atualizado 2026]

Se você está desenvolvendo com Next.js e de repente aparece o erro “Hydration failed because the initial UI does not match what was rendered on the server”, saiba que você não está sozinho. Este erro é especialmente frequente em ambientes React 19 e Next.js 15 com App Router, com milhares de comentários no GitHub Discussions e constantemente em alta no Stack Overflow. Este artigo fornece um guia completo atualizado para 2026 para identificar as causas e aplicar soluções específicas, com exemplos de código reais.

O Que É Este Erro? Sintomas que Você Vai Experimentar

Um Hydration Error (erro de hidratação) no Next.js ocorre quando o HTML renderizado no servidor não corresponde ao HTML que o React tenta renderizar no cliente durante a renderização inicial.

Especificamente, as seguintes mensagens de erro aparecerão no console de desenvolvimento do seu navegador:

Unhandled Runtime Error
Error: Hydration failed because the initial UI does not match what was rendered on the server.

Você também pode ver estas variações:

Error: Text content does not match server-rendered HTML.
Error: There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.

Quando este erro ocorre, o Next.js descarta o resultado da renderização do lado do servidor (SSR) e re-renderiza toda a página no cliente. Isso leva a várias consequências graves:

  • Degradação significativa de desempenho: Todos os benefícios do SSR são perdidos, tornando o carregamento inicial da página mais lento
  • Impacto negativo no SEO: Os crawlers dos motores de busca podem não receber o HTML correto
  • Má experiência do usuário: Ocorre cintilação de tela (flash de conteúdo)
  • Experiência do desenvolvedor prejudicada: O console fica cheio de erros, facilitando perder outros problemas

No Next.js 15 combinado com React 19, as verificações de consistência de hidratação ficaram mais rigorosas, e casos que antes eram apenas avisos agora são tratados como erros.

Causas Deste Erro

Causa 1: Uso de APIs Exclusivas do Navegador (window / localStorage / document)

A causa mais frequente é referenciar diretamente APIs exclusivas do navegador como window, localStorage e document durante a renderização. Esses objetos não existem no lado do servidor.

No servidor, esses objetos são undefined. Portanto, usar ramificações condicionais como typeof window !== 'undefined' na lógica de renderização causa saídas diferentes entre servidor e cliente, provocando erros de hidratação.

// ERRADO: Isso causa um erro de hidratação
function MyComponent() {
  const isClient = typeof window !== 'undefined';
  return <div>{isClient ? 'Cliente' : 'Servidor'}</div>;
}

Causa 2: Valores Não Determinísticos Como Timestamps e Números Aleatórios

Usar funções que retornam valores diferentes a cada chamada, como new Date() ou Math.random(), durante a renderização causa incompatibilidades entre a saída do servidor e do cliente.

// ERRADO: O timestamp difere entre servidor e cliente
function Clock() {
  return <p>Hora atual: {new Date().toLocaleTimeString()}</p>;
}

Sempre há um atraso entre quando o servidor renderiza e quando o cliente hidrata, então o horário exibido naturalmente será diferente, resultando em um erro de incompatibilidade de conteúdo de texto.

Causa 3: Estrutura de Aninhamento HTML Inválida

Estruturas de aninhamento inválidas que violam as especificações HTML (por exemplo, colocar um <div> dentro de uma tag <p>) fazem com que o parser HTML do navegador corrija automaticamente o DOM, criando uma estrutura DOM diferente do HTML renderizado no servidor.

// ERRADO: <div> não pode ser colocado dentro de <p>
function BadNesting() {
  return (
    <p>
      Texto
      <div>Este é um aninhamento inválido</div>
    </p>
  );
}

O navegador corrige automaticamente este HTML inválido, causando uma incompatibilidade entre o HTML enviado pelo servidor e o DOM do navegador.

Causa 4: Extensões do Navegador Modificando o DOM

Extensões do navegador como Colorzilla e Grammarly podem injetar atributos ou elementos HTML no DOM da página. Por exemplo, o Colorzilla adiciona um atributo cz-shortcut-listen="true" à tag <body>. Isso foi relatado como particularmente problemático em ambientes Next.js 15 + React 19.

Causa 5: Scripts de Terceiros e Inconsistências de Conteúdo CMS

Scripts externos (tags de publicidade, analytics, etc.) que modificam o DOM, ou conteúdo HTML obtido de CMS que contém estruturas de aninhamento inválidas, podem acionar erros de hidratação.

Solução 1: Isolar Lógica Exclusiva do Cliente com o Hook useEffect (Recomendado)

A solução mais eficaz e recomendada é mover a lógica dependente do navegador para o hook useEffect e produzir apenas valores determinísticos durante a renderização inicial.

Passo 1: Implementar o Padrão de Gerenciamento de Estado do Cliente

Primeiro, defina um State com o mesmo valor inicial para servidor e cliente, depois atualize para o valor do lado do cliente com useEffect.

'use client';

import { useState, useEffect } from 'react';

function ThemeProvider({ children }) {
  // Usar um valor seguro para o servidor como estado inicial
  const [theme, setTheme] = useState('light');
  const [mounted, setMounted] = useState(false);

  useEffect(() => {
    // Só acessar localStorage no cliente
    const savedTheme = localStorage.getItem('theme') || 'light';
    setTheme(savedTheme);
    setMounted(true);
  }, []);

  // Antes da montagem, retornar a mesma saída que o servidor
  if (!mounted) {
    return <div data-theme="light">{children}</div>;
  }

  return <div data-theme={theme}>{children}</div>;
}

Passo 2: Definir Timestamps e Valores Aleatórios Dentro do useEffect

'use client';

import { useState, useEffect } from 'react';

function Clock() {
  const [currentTime, setCurrentTime] = useState('');

  useEffect(() => {
    const updateTime = () => {
      setCurrentTime(new Date().toLocaleTimeString());
    };
    updateTime();
    const timer = setInterval(updateTime, 1000);
    return () => clearInterval(timer);
  }, []);

  // Mostrar string vazia na renderização inicial (corresponde ao servidor)
  return <p>Hora atual: {currentTime || 'Carregando...'}</p>;
}

Passo 3: Tornar Reutilizável com um Hook Personalizado

Se você usar este padrão em vários componentes, extraia-o para um hook personalizado.

'use client';

import { useState, useEffect } from 'react';

// Hook personalizado para gerenciar o estado de montagem
function useHasMounted() {
  const [hasMounted, setHasMounted] = useState(false);
  useEffect(() => {
    setHasMounted(true);
  }, []);
  return hasMounted;
}

// Exemplo de uso
function ClientOnlyComponent() {
  const hasMounted = useHasMounted();

  if (!hasMounted) {
    return <div>Carregando...</div>;
  }

  return <div>Largura da janela: {window.innerWidth}px</div>;
}

Notas Importantes

  • useEffect não é executado no lado do servidor, então APIs do navegador podem ser usadas com segurança dentro dele
  • Exiba um placeholder ou UI de fallback durante a renderização inicial para manter a experiência do usuário
  • Este padrão pode mostrar brevemente um placeholder, mas é muito mais leve do que a re-renderização de toda a página causada por erros de hidratação

Solução 2: Desabilitar SSR com next/dynamic

Para componentes difíceis de lidar com o padrão useEffect (por exemplo, bibliotecas de terceiros que dependem fortemente de APIs do navegador), desabilitar o SSR completamente usando next/dynamic é eficaz.

import dynamic from 'next/dynamic';

// Importar com SSR desabilitado
const MapComponent = dynamic(
  () => import('../components/Map'),
  {
    ssr: false,
    loading: () => <div className="map-placeholder">Carregando mapa...</div>
  }
);

// Exemplo de uso
function LocationPage() {
  return (
    <div>
      <h1>Localização da Loja</h1>
      <MapComponent />
    </div>
  );
}

Este método é especialmente eficaz nos seguintes casos:

  • Bibliotecas de mapas (Leaflet, Google Maps, etc.): Porque dependem do objeto window
  • Editores de texto rico (Quill, TipTap, etc.): Porque manipulam diretamente o objeto document
  • Bibliotecas de gráficos (Chart.js, D3.js, etc.): Porque realizam operações DOM de SVG ou Canvas

No entanto, componentes com SSR desabilitado não são incluídos no HTML inicial, então evite isso para conteúdo crítico para SEO. A melhor prática é limitar o uso a elementos interativos que não sejam conteúdo principal (mapas, gráficos, editores, etc.).

Solução 3: Corrigir Aninhamento HTML e Uso Apropriado de suppressHydrationWarning

Como solução avançada, você pode corrigir as estruturas de aninhamento HTML e usar suppressHydrationWarning para casos inevitáveis.

Corrigir Aninhamento HTML Inválido

Primeiro, verifique se há aninhamento HTML inválido. Aqui estão os padrões inválidos comuns e suas correções:

// ERRADO: <div> dentro de <p>
<p>Texto<div>Elemento de bloco</div></p>

// CERTO: Mudar para <div>
<div>Texto<div>Elemento de bloco</div></div>

// ERRADO: <a> dentro de <a>
<a href="/pai">
  Link pai
  <a href="/filho">Link filho</a>
</a>

// CERTO: Remover aninhamento
<div>
  <a href="/pai">Link pai</a>
  <a href="/filho">Link filho</a>
</div>

Uso Limitado de suppressHydrationWarning

Para elementos pequenos onde diferenças de valor entre servidor e cliente são inevitáveis (como exibição de timestamps), você pode usar suppressHydrationWarning.

// Usar apenas quando diferenças são aceitáveis, como timestamps
<time suppressHydrationWarning>
  {new Date().toLocaleDateString()}
</time>

Nota importante: suppressHydrationWarning apenas oculta o erro; não resolve o problema subjacente. Aplica-se apenas a uma profundidade de nós de texto, e incompatibilidades em elementos filhos continuam sendo detectadas. Use com moderação e apenas quando absolutamente necessário.

Lidando com Extensões do Navegador

Se extensões do navegador são a causa, você pode adicionar suppressHydrationWarning à tag <body>.

// app/layout.tsx
export default function RootLayout({ children }) {
  return (
    <html lang="pt">
      <body suppressHydrationWarning>{children}</body>
    </html>
  );
}

Isso suprime avisos causados por atributos que extensões adicionam ao body. No entanto, incompatibilidades nos filhos diretos do body continuam sendo detectadas.

Como Prevenir Este Erro

Para prevenir proativamente erros de hidratação, incorpore estas medidas preventivas ao seu fluxo de trabalho de desenvolvimento diário.

1. Mantenha a Renderização Inicial “Determinística”

O princípio mais importante é garantir saída idêntica para as renderizações iniciais do servidor e do cliente. Valores dependentes do ambiente (APIs do navegador, timestamps, números aleatórios, etc.) devem sempre ser colocados dentro do useEffect.

2. Separe Claramente Server Components e Client Components

No App Router do Next.js 15, os componentes são Server Components por padrão. Ao usar APIs do navegador ou hooks do React (useState, useEffect, etc.), sempre adicione a diretiva 'use client' no topo do arquivo.

3. Aproveite Regras do ESLint

eslint-plugin-react inclui regras para detectar aninhamento HTML inválido (como jsx-no-invalid-html-nesting). Integre-as ao seu pipeline de CI para verificações automáticas.

4. Testes Regulares

Teste periodicamente em modo anônimo (navegação privada) ou com extensões do navegador desabilitadas para isolar erros relacionados a extensões.

5. Design Responsivo Baseado em CSS

Em vez de ramificar a marcação com base no tamanho do viewport, use CSS media queries ou display: none para alternar a visibilidade, mantendo a estrutura HTML consistente entre servidor e cliente.

// ERRADO: Ramificação por viewport em JS
{isMobile ? <MobileNav /> : <DesktopNav />}

// CERTO: Alternância baseada em CSS
<MobileNav className="block md:hidden" />
<DesktopNav className="hidden md:block" />

Resumo

O Hydration Error do Next.js “Hydration failed because the initial UI does not match what was rendered on the server” ocorre devido a incompatibilidades entre SSR/SSG e renderização do lado do cliente, sendo um dos erros mais frequentemente encontrados no desenvolvimento Next.js.

Pontos-chave:

  • O problema central é “resultados de renderização inicial diferentes entre servidor e cliente”
  • Causas mais comuns: uso direto de APIs exclusivas do navegador, renderização de valores não determinísticos, aninhamento HTML inválido
  • Soluções recomendadas: renderização em estágios com useEffect, desabilitar SSR com next/dynamic quando necessário
  • suppressHydrationWarning deve ser usado como último recurso, com escopo limitado
  • Prevenção: manter renderizações iniciais determinísticas, separar claramente Server/Client Components

Se estas soluções não resolverem o problema, verifique o seguinte:

  1. Atualize Next.js e React para as versões mais recentes
  2. Delete as pastas node_modules e .next e reconstrua
  3. Pesquise casos semelhantes no GitHub Discussions do Next.js (https://github.com/vercel/next.js/discussions)
  4. Crie uma reprodução mínima e reporte como Issue

Referências

コメント

タイトルとURLをコピーしました