# Hydration mismatch in React 19: perché "Text content does not match server-rendered HTML"

> Le tre vere cause degli errori di hydration nelle app React con SSR, come leggere il diff di React in sviluppo, e quando suppressHydrationWarning è davvero la scelta giusta.

- Autore / Author: Diego Betto (https://diegobetto.com/about/)
- Pubblicato / Published: 2026-09-18
- URL: https://diegobetto.com/react-hydration-mismatch/
- Tag: react, 19, hydration, ssr, server components

Renderizzi lo stesso componente sul server e sul client — stesse props, stesso codice — eppure la console del browser mostra questo appena la pagina si carica:

> **Errore**
>
> Hydration failed because the server rendered HTML didn't match the client. As a result this tree
> will be regenerated on the client, which can affect performance.

Oppure, a seconda del framework e della versione di React, una variante più specifica:

> **Errore**
>
> Text content does not match server-rendered HTML.

L'hydration è React che collega event handler e informazioni interne all'HTML già renderizzato sul server — non renderizza da zero, _riusa_ il DOM esistente. Funziona solo se ciò che React renderizzerebbe sul client è identico byte per byte a ciò che il server ha effettivamente inviato. Quando non lo è, ottieni questo errore.

## Causa #1: valori che differiscono tra server e client

Il colpevole classico: qualsiasi cosa produca un risultato diverso a seconda di _dove_ gira.

```jsx
function Timestamp() {
  // Il server renderizza un valore, il client ne renderizza uno diverso un attimo dopo
  return <span>{new Date().toLocaleTimeString()}</span>;
}
```

```jsx
function Greeting() {
  // Math.random(), navigator.language, o qualsiasi cosa legga window/localStorage
  // non ha un valore definito durante l'SSR e ne ha uno reale sul client
  const id = Math.random();
  return <div data-id={id}>Ciao</div>;
}
```

La correzione è calcolare il valore solo sul client (dentro un `useEffect`, dopo il render iniziale che deve corrispondere per l'hydration) oppure passarlo giù dal server come prop, così entrambi gli ambienti renderizzano esattamente la stessa cosa:

```jsx
function Timestamp() {
  const [time, setTime] = useState(null);

  useEffect(() => {
    setTime(new Date().toLocaleTimeString());
  }, []);

  // Non renderizza nulla (o un placeholder stabile) sia sul server sia al primo render client
  return <span>{time ?? "--:--:--"}</span>;
}
```

## Causa #2: nesting HTML non valido

I browser "correggono" silenziosamente l'HTML non valido durante il parsing — spostando elementi in modi che il tuo albero React non ha mai richiesto. Il server invia il markup non valido così com'è, il browser lo reinterpreta durante il parsing, e il render client-side di React non corrisponde a ciò che è effettivamente finito nel DOM.

```jsx
// Non valido: <div> non è permesso dentro <p> secondo la spec HTML
function Card() {
  return (
    <p>
      <div>Del contenuto</div>
    </p>
  );
}
```

Il browser chiude silenziosamente il `<p>` prima del `<div>`, producendo una struttura DOM che React non ha renderizzato. È facile introdurre questo problema per errore — per esempio un componente `<Card>` che renderizza un `<div>` usato dentro un componente `<Text>` che avvolge i children in un `<p>`.

## Causa #3: estensioni del browser che modificano il DOM prima dell'hydration

Questo caso non è affatto un tuo bug. Alcune estensioni del browser (password manager, ad blocker, strumenti di accessibilità) iniettano attributi o elementi nella pagina _prima_ che React faccia l'hydration — React vede quindi un DOM che non corrisponde a ciò che si aspetta e protesta, anche se il tuo codice è corretto.

> **Come riconoscerlo**
>
> Se il mismatch capita solo per alcuni visitatori, solo in produzione (non nel tuo ambiente di
> sviluppo locale), o il diff riportato mostra un attributo che non hai mai scritto (come{" "}
> <code>data-lastpass-icon-root</code>), è molto probabile che sia un'estensione. Prova a riprodurlo
> in una finestra in incognito con le estensioni disabilitate prima di perdere tempo a debuggare il
> tuo codice.

## Come leggere il diff di hydration di React

In sviluppo, React mostra un diff tra ciò che il server ha inviato e ciò che il client renderizzerebbe — leggilo come leggeresti un diff git: la riga `-` è ciò che ha prodotto il server, la riga `+` è ciò che il client vuole produrre. Il valore che differisce è il tuo punto di partenza per restringere il campo alla causa #1 sopra.

## Quando `suppressHydrationWarning` è legittimo

```jsx
<time suppressHydrationWarning>{new Date().toLocaleTimeString()}</time>
```

Questo dice a React "so che il testo di questo specifico elemento differirà tra server e client, non avvisare, e non ri-renderizzarlo durante l'hydration." È lo strumento giusto esattamente quando un valore è genuinamente atteso essere diverso (un orologio live, un ID generato casualmente che non influisce sul layout) e hai deliberatamente deciso che il mismatch è innocuo. È lo strumento sbagliato come modo per silenziare un errore che non hai davvero diagnosticato — sopprime il warning solo su quell'elemento, non l'incoerenza di fondo, e non risolve affatto il nesting HTML non valido o un valore genuinamente rotto.

## FAQ

> **Succede solo con Next.js?**
>
> No — è una proprietà di qualsiasi app React che fa server-side rendering, incluse le isole Astro,
> Remix, e setup SSR personalizzati. La causa e la correzione sono le stesse indipendentemente dal
> framework.

> **I React Server Components rendono questo più o meno probabile?**
>
> Cambia dove si presenta il rischio più che eliminarlo. I Server Components stessi non fanno
> hydration (non girano mai nel browser), ma qualsiasi Client Component che renderizzano deve
> comunque corrispondere tra l'HTML renderizzato dal server e il primo render del client, quindi le
> stesse tre cause sopra si applicano a quel confine.

> **Un hydration mismatch è un problema di performance, non solo un bug?**
>
> Entrambe le cose. Quando React rileva un mismatch, butta via il sottoalbero non corrispondente e
> lo ri-renderizza interamente sul client — il che significa perdere il beneficio dell'SSR (primo
> paint veloce) per quella parte della pagina, oltre all'errore in console.

## Conclusione

Gli errori di hydration si riducono sempre al client che renderizza qualcosa di diverso da ciò che il server ha inviato — un valore che differisce legittimamente per ambiente, HTML che il browser ha ristrutturato silenziosamente, o un'estensione del browser che inietta nel DOM prima che React ci arrivi. `suppressHydrationWarning` è un bisturi per il primo caso quando la differenza è davvero attesa e innocua, non una correzione generica — diagnostica quale delle tre hai davanti prima di usarlo.

**Riferimenti**

- [Handling different client and server content – documentazione React](https://react.dev/reference/react-dom/client/hydrateRoot#handling-different-client-and-server-content)
- [suppressHydrationWarning – documentazione React](https://react.dev/reference/react-dom/components/common#suppresshydrationwarning)
