todos os posts

Notas sobre hidratação parcial no App Router

Next.jsPerformance2 min de leitura

O erro apareceu só em produção, só no primeiro carregamento, e só para quem chegava por um link compartilhado. Levei dois dias para entender que o problema não era hidratação — era eu ter assumido que o servidor e o cliente concordavam sobre que horas eram.

O sintoma

Um Server Component renderizava uma data formatada. No cliente, o mesmo nó vinha diferente por um dia inteiro. O React reclamava, descartava a árvore e re-renderizava tudo do zero — o que, numa página cheia de animação, é bem mais caro do que parece.

Hidratação não é o React "ligando" o HTML. É o React conferindo se o que ele renderizaria bate com o que já está lá. Quando não bate, ele não corrige: ele desiste e recomeça.

Timeline do profiler mostrando a re-renderização completa após o mismatch

As três suposições

  1. Que new Date() no servidor e no cliente produziriam o mesmo dia. Não produzem, se o servidor está em UTC e você não está.
  2. Que um mismatch de hidratação era um aviso, não um erro. É um erro de performance, e um dos caros.
  3. Que o problema estava no componente que reclamou. Estava no componente que passou a prop.

O que eu deveria ter olhado primeiro

  • A diferença exata entre o HTML do servidor e o primeiro render do cliente
  • Se o valor problemático era derivado de algo não-determinístico
  • Se o componente precisava mesmo ser um Client Component

A correção

Formatar no servidor e mandar a string pronta. O timezone vira uma decisão explícita em vez de um acidente do ambiente:

type Props = { publishedAt: string };

export function PublishedAt({ publishedAt }: Props) {
  // Já formatado no servidor. O cliente não recalcula nada, então não há
  // nada sobre o que discordar.
  return <time dateTime={publishedAt}>{formatPostDate(publishedAt)}</time>;
}

A regra que ficou: se um valor depende de Date, Math.random ou window, ele não atravessa a fronteira servidor/cliente como cálculo — ele atravessa como resultado.

Tem mais coisa na mesma linha em todos os posts, e a documentação do React sobre hydration mismatches é mais útil do que a mensagem de erro sugere.