Servidor em UTC e navegador no Brasil: por que a data aparece errada por um dia?

Data renderizada no servidor em UTC e no navegador em UTC-3 diverge à noite e gera erro de hidratação do React. Entenda a causa e como fixar o fuso.

Pedro Henrique Quadroatualizado em 4 min de leitura

Porque a mesma data é formatada duas vezes, uma no servidor e outra no navegador, e cada lugar usa o seu fuso horário. Se o servidor roda em UTC e o usuário está no Brasil, em UTC-3, depois das 21 horas de Brasília o servidor já está no dia seguinte. O React percebe a diferença e acusa erro de hidratação, e as datas formatadas só no servidor aparecem um dia à frente. A correção é fixar o fuso no servidor e também dentro do formatador de datas.

O que a documentação diz

Conferi as fontes em 10 de outubro de 2026. Hidratação é o momento em que o React pega o HTML pronto que veio do servidor e o torna interativo no navegador. Para isso ele refaz a renderização e compara com o HTML recebido. A documentação do hydrateRoot diz que o React espera conteúdo idêntico ao do servidor e manda tratar diferenças "como bugs". Ela dá como exemplo justamente uma data formatada com new Date().toLocaleDateString().

O código de erro 425 do React é "Text content does not match server-rendered HTML", ou seja, o texto não bate com o HTML do servidor. Pelo guia de erros do Next.js, usar APIs que dependem do tempo, como o construtor Date(), é uma causa conhecida. O guia oferece três saídas: renderizar o valor só no cliente depois do primeiro desenho, desligar o desenho no servidor naquele componente, ou usar suppressHydrationWarning. Esta última apenas silencia o aviso, e a documentação a chama de "escape hatch" para não ser usada demais. O React também não corrige o texto que ficou diferente.

Sobre o fuso, a MDN diz que toLocaleDateString formata no fuso local, e que o resultado sem argumentos depende da implementação, do idioma e do fuso padrão. A opção timeZone existe para fixar isso. Já a documentação do Node.js diz que a variável de ambiente TZ define a configuração de fuso do processo e aceita nomes como Europe/Paris ou America/New_York. O nome America/Sao_Paulo é do mesmo tipo.

Não vi documentado o fuso padrão de um container. No meu caso era UTC, mas isso depende da imagem que você usa.

O que vi em produção

Em 2 de setembro de 2026, numa aplicação web em que trabalhei, rodei um teste de ponta a ponta no ambiente de produção. A tela mostrava uma lista de linhas com datas, formatadas com toLocaleDateString num componente de cliente. O servidor, num container em UTC, desenhava a lista primeiro. O navegador, em UTC-3, desenhava de novo.

Depois das 21 horas de Brasília, o dia era diferente nos dois lados. O React acusou o erro 425 em cada linha da lista. Além disso, as datas que só existiam no servidor, como "Enviado em", saíam no dia seguinte, sem nenhum aviso.

Um detalhe que atrasou o diagnóstico: nenhum teste local pegou. A minha máquina de desenvolvimento já estava no fuso certo, então servidor e navegador concordavam. O defeito só aparece quando os dois ambientes têm fusos diferentes, e à noite.

Por que acontece

Uma data é um instante no tempo, e o dia que você lê depende do fuso. Às 22h30 em Brasília, em UTC já são 01h30 do dia seguinte. Se o servidor e o navegador usam fusos diferentes, a mesma data vira dois textos diferentes. O React compara os dois textos, vê que não batem e reclama.

A parte pior é o erro silencioso. O que o servidor escreveu sozinho não passa por comparação nenhuma. Ninguém avisa que a data está errada, e o usuário vê o dia seguinte.

O que fazer

  1. Defina o fuso do container, no arquivo de ambiente: TZ=America/Sao_Paulo. O Node passa a formatar datas nesse fuso.
  2. Como camada extra, passe o fuso direto no formatador, para o resultado não depender do ambiente:
new Date(valor).toLocaleDateString('pt-BR', {
  timeZone: 'America/Sao_Paulo',
})
  1. Teste com o relógio da máquina deslocado, ou simule o servidor em UTC com TZ=UTC ao subir o ambiente local. Depois das 21h, no horário do Brasil, ou com uma data fixa nesse intervalo, o erro aparece.
  2. Evite suppressHydrationWarning como remédio. Ele esconde o aviso, mas o React não corrige o texto, e o servidor continua escrevendo a data errada.
  3. Se o seu produto atende gente em vários fusos, formatar no servidor com um fuso único mostra a todos o mesmo dia. Nesse caso decida de propósito qual fuso vale, em vez de deixar o ambiente decidir.

Se datas, fusos e erros que só aparecem em produção estão te custando tempo, veja como eu desenvolvo aplicativos e sistemas.

Fontes

  1. React: erro 425, texto não bate com o HTML do servidor
  2. React: hydrateRoot, o conteúdo precisa ser idêntico e mismatch é bug
  3. Next.js: erro de hidratação, causas com Date e uso de suppressHydrationWarning
  4. MDN: toLocaleDateString usa o fuso local por padrão e aceita timeZone
  5. Node.js: variável TZ define o fuso do processo
Pedro Henrique QuadroEngenheiro de software e IA. Constrói aplicativos, sistemas, painéis e agentes de IA em produção, e tem código aceito no Supabase, no Kestra e no QuestDB.

Continue lendo