Errori e fix

Hydration failed because the initial UI does not match: cause e correzioni

React 18 e 19 confrontano l'HTML del server con quello del primo render client. Se differiscono, l'albero viene rigenerato: ecco le cause reali e come isolarle.

In sintesi

  • Il messaggio indica un render non deterministico: date, numeri casuali, localStorage o HTML non valido.
  • La correzione è spostare il valore variabile dopo il mount o renderizzarlo dal server, non silenziare l'avviso.
  • `suppressHydrationWarning` vale solo per il testo di un singolo elemento e non ripara la causa.

Risposta breve: il primo render sul client ha prodotto un albero diverso da quello che il server ha scritto nell'HTML. React butta via l'HTML del server e rifà il render: la pagina appare, ma perdi il vantaggio dell'SSR e vedi un lampo di contenuto.

Il messaggio esatto

Hydration failed because the initial UI does not match what was
rendered on the server. There was an error while hydrating. Because
the error happened outside of a Suspense boundary, the entire root
will switch to client rendering.

La seconda frase è la più importante: outside of a Suspense boundary. Se il componente che diverge sta dentro un confine di Suspense, React recupera solo quel ramo; altrimenti ricostruisce tutta la radice.

Le quattro cause reali

CausaSintomo tipicoCorrezione
Valore non deterministico nel renderdate, contatori, numeri casualisposta il calcolo in un effetto
HTML non valido<div> dentro <p>, <div> dentro <tbody>correggi l'annidamento
Dato del browser letto nel renderlocalStorage, matchMedia, tema salvatoleggi dopo il mount, server e client partono uguali
Estensione del browserattributi aggiunti prima dell'idratazioneverifica in finestra anonima

La correzione, in ordine

Prima di toccare il codice, riproduci il problema in una finestra anonima senza estensioni. Se sparisce, il colpevole non è la tua applicazione.

Se resta, cerca un render non deterministico. Il caso più comune è una data: il server è in UTC, il browser nel fuso dell'utente, e new Date().toLocaleString() produce due stringhe diverse.

// sbagliato: il server e il client formattano in fusi diversi
function LastUpdate() {
  return {new Date().toLocaleString()};
}

// corretto: il testo arriva già formattato dal server
function LastUpdate({ label }) {
  return {label};
}

Quando il valore esiste solo nel browser, inizializza con un valore neutro e aggiorna dopo il mount. Lo stato intermedio è identico sui due lati, quindi l'idratazione coincide.

"use client";
import { useEffect, useState } from "react";

export function LocalTime() {
  const [now, setNow] = useState(null);
  useEffect(() => setNow(new Date().toLocaleTimeString()), []);
  return {now ?? "—"};
}

Per gli identificatori generati, React offre useId(): produce lo stesso valore sul server e sul client per lo stesso albero.

Quando la correzione non funziona

suppressHydrationWarning silenzia l'avviso per il testo di un singolo elemento, non corregge la causa. Se lo metti su un contenitore grande, React smette di segnalare il problema ma continua a rigenerare l'albero: hai guadagnato silenzio, non prestazioni. L'altra eccezione è l'HTML generato da una libreria esterna che non puoi cambiare: in quel caso isola il componente dietro un mount client-side.

Come prevenirlo

  • Regola unica: il primo render deve dipendere solo dalle props, mai da orologio, browser o ambiente.
  • Valida l'HTML: i nesting vietati producono mismatch anche quando il markup sembra corretto.
  • Nella CI, apri le pagine principali con JavaScript disattivato: quello che vedi è il render del server.

Dove continuare

Nel catalogo trovi componenti React già scritti per l'idratazione e blocchi hero con preview; le icone sono in /icons. Approfondisci con la struttura App Router e dark mode senza un secondo design system; il confronto tra primitive è in Radix, Base UI e React Aria.

Domande frequenti

La pagina funziona lo stesso: devo correggere l'errore?

Sì. React butta via l'albero del server e rifà il render sul client: perdi il vantaggio dell'SSR e paghi un flash.

Perché succede solo in produzione?

In sviluppo il server e il client girano sullo stesso fuso orario e sulla stessa macchina. In produzione il server è in UTC e il browser no, quindi date formattate e `toLocaleString` divergono.

Un'estensione del browser può causarlo?

Sì. Estensioni che modificano il DOM prima dell'idratazione producono lo stesso messaggio. Verifica in una finestra anonima senza estensioni.

suppressHydrationWarning è una soluzione?

È un cerotto per un singolo nodo di testo, utile solo per contenuti che cambiano legittimamente come un timestamp. Non corregge nesting non valido né valori casuali.

Come trovo il componente colpevole?

React 19 mostra uno stack con il componente. Se lo stack è generico, disabilita a metà i blocchi della pagina: la differenza tra due render ti dice il ramo.