Diego Betto
Foto di Akira Hojo su Unsplash

Diego Betto · 18 settembre 2026 · 5 min di lettura

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.

Condividi:XLinkedInFacebookWhatsApp

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.

function Timestamp() {
  // Il server renderizza un valore, il client ne renderizza uno diverso un attimo dopo
  return <span>{new Date().toLocaleTimeString()}</span>;
}
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:

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.

// 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 data-lastpass-icon-root), è 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

<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

Condividi:XLinkedInFacebookWhatsApp
Diego Betto

Scritto da

Diego Betto

Co-Fondatore & CTO presso PAPION. Senior full-stack engineer specializzato in React, TypeScript, Node.js e sicurezza applicativa.