# Hydration failed because the initial UI does not match: causes and fixes

React 18 and 19 compare the server HTML with the first client render. When they differ, React rebuilds the tree: real causes and how to isolate them.

> Page: https://ui.eneawork.it/blog/hydration-failed-initial-ui-does-not-match
> Markdown: https://ui.eneawork.it/blog/hydration-failed-initial-ui-does-not-match.md

*2026-11-01*

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.

La domanda «React 18: Hydration failed because the initial UI does not match what was rendered on the server» vale 341 voti su Stack Overflow (letto il 31 ottobre 2026 con la Stack Exchange API): fonte. Il catalogo EneUI misura 111.985 componenti, 1.556 blocchi, 12.808 icone da 1.472 librerie, letti da /api/stats il 1 novembre 2026. Ogni comando qui sotto è eseguibile in locale: nessun passaggio richiede un account.

## 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

## 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

## 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.

## FAQ

### 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.
