Volver a las notas

Errores de hidratación en Next.js: por qué pasan y cómo arreglarlos

30 de julio de 2026·
nextjsreactssrhidratacion

Nota revisada y reescrita el 18 de octubre de 2026: la versión original tenía errores y datos sin fuente.

Hydration failed because the server rendered HTML didn't match the client (o Text content does not match server-rendered HTML en versiones anteriores). El servidor ha generado un HTML, el navegador ejecuta el mismo componente para "engancharle" los eventos, y el resultado no coincide. React no puede reutilizar ese HTML y vuelve a renderizar en el cliente lo que no cuadra. Lo pagas en rendimiento, en saltos visuales y, a veces, en estado perdido.

La regla es simple: el primer render en el cliente tiene que producir exactamente el mismo HTML que el servidor.

Las causas habituales

1. Valores que cambian entre servidor y cliente

// El servidor está en UTC y el usuario en Madrid: el texto no coincide
<p>Actualizado: {new Date().toLocaleString()}</p>

// Distinto en cada ejecución
<div id={Math.random().toString(36)} />

2. Comprobar si estás en el navegador durante el render

// En el servidor da false y en el cliente true: dos HTML distintos
const isMobile = typeof window !== 'undefined' && window.innerWidth < 768;

Pasa lo mismo con localStorage, navigator o las cookies leídas con document.cookie.

3. HTML inválido. Un <div> dentro de un <p>, o un <a> dentro de otro <a>. El navegador "corrige" el HTML del servidor al parsearlo, y lo que React encuentra ya no es lo que el servidor mandó.

4. Extensiones del navegador que inyectan atributos o nodos (traductores, gestores de contraseñas, Grammarly). Si el error desaparece en una ventana de incógnito sin extensiones, es esto.

Cómo arreglarlo bien

Mover lo que depende del navegador a un efecto. Los efectos no corren en el servidor y se ejecutan después de hidratar:

function LocalTime({ iso }) {
  const [text, setText] = useState(null);
  useEffect(() => setText(new Date(iso).toLocaleString()), [iso]);
  return <time dateTime={iso}>{text ?? iso.slice(0, 10)}</time>;
}

El servidor y el primer render del cliente pintan lo mismo (la fecha ISO). Después, el efecto la cambia a la hora local.

Calcular el valor en el servidor y pasarlo como prop. Si el dato se puede saber en el servidor (la fecha formateada con una zona horaria fija o la región por cabeceras), no hay nada que desincronizar.

useId para identificadores. Genera el mismo ID en servidor y cliente, que es lo que necesitas para htmlFor o aria-describedby:

const id = useId();
return (<><label htmlFor={id}>Email</label><input id={id} /></>);

No renderizar en el servidor un componente que solo tiene sentido en el cliente. Un mapa o un editor que necesita window:

import dynamic from 'next/dynamic';
const Map = dynamic(() => import('../components/Map'), { ssr: false });

En el App Router, ssr: false solo se permite dentro de un Client Component.

Lo que no hay que hacer

suppressHydrationWarning solo silencia el aviso, y solo afecta a un nivel de profundidad: al texto y los atributos de ese elemento, no a sus hijos. Está pensado para casos inevitables, como una marca de tiempo. Si lo usas para tapar un typeof window en el render, el error sigue ahí y solo deja de avisar.

Encontrar el componente

En desarrollo, React 19 y Next.js 15 muestran un diff del HTML del servidor frente al del cliente, con el componente donde empieza la diferencia. En producción el mensaje viene minificado. Para encontrar el componente, reprodúcelo con next dev o con un build de producción sin minificar.

Fuentes