Cookies
This website uses cookies to improve the user experience, more information on the legal information path.
Notas
0
años
0
meses
0
días
0
horas
0
minus
0
segus
00
Esta es la imagen de fondo
6

Cómo arreglar Minified React error #425 (error de hidratación)

Respuesta rápida

El error #425 de React —"Text content does not match server-rendered HTML"— es un error de hidratación: el servidor renderizó un valor y el navegador otro distinto, así que React descarta el HTML del servidor para ese subárbol. El arreglo depende de por qué difieren:

  • El valor difiere de forma legítima (una fecha, una hora) → añade suppressHydrationWarning (Arreglo 1).
  • El valor solo existe en el navegador (window, localStorage) → renderízalo tras el montaje (Arreglo 2).
  • El componente entero es solo-cliente → cárgalo con next/dynamic y ssr: false (Arreglo 3).
  • Tu HTML es inválido (un <div> dentro de un <p>) → corrige el anidamiento (Arreglo 4).

El error completo en producción es: Uncaught Error: Minified React error visit https://reactjs.org/docs/error-decoder.html?invariant=425 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.

Qué significa realmente "Minified React error #425"

En una build de producción React envía los mensajes de error minificados para mantener el bundle pequeño, así que en vez de una frase recibes un número y un enlace descodificador (invariant=425). El #425 descodifica a "Text content does not match server-rendered HTML".

Eso solo ocurre con renderizado en servidor (Next.js, Remix, renderToString): React renderiza el HTML una vez en el servidor, lo envía al navegador y luego hidrata —reutiliza ese HTML y solo engancha los manejadores de eventos, esperando que el primer render del cliente produzca exactamente la misma salida—. Cuando un trozo de texto sale distinto, la hidratación de ese subárbol falla y aparece el #425.

Como el mensaje está minificado, el paso de depuración más útil es reproducirlo en una build de desarrollo: ahí React imprime el mensaje completo y señala el elemento exacto, lo que convierte las conjeturas en un arreglo de una línea (ver "Cómo ver el error real" más abajo).

Por qué ocurren los errores de hidratación

Todo #425 se reduce a salida del servidor ≠ primera salida del cliente. Los sospechosos habituales:

CausaPor qué difiere
new Date() / toLocaleStringEl reloj/zona del servidor ≠ el reloj/zona del navegador
Math.random(), Date.now(), uuid()Un valor nuevo en cada render, servidor y cliente nunca coinciden
Formato de número/moneda por localeEl locale del servidor ≠ el del navegador del usuario
window, localStorage, navigatorIndefinidos en el servidor, presentes en el navegador
Anidamiento HTML inválidoEl navegador "arregla" tu HTML, así que el DOM ≠ lo que renderizó React
Extensiones del navegadorModifican el DOM antes de que React hidrate

Diagrama de un error de hidratación: el servidor renderiza la hora como 14:32, el navegador la vuelve a renderizar como 14:33 al hidratar, el texto no coincide y React lanza el error #425.

El ejemplo de abajo es el caso clásico: un reloj en vivo.

El código que provocaba el error

DateAndHour.tsx

const DateAndHour = () => {
const { locale } = useRouter();
const [date, setDate] = React.useState(new Date());
const day = date.toLocaleDateString(locale, { weekday: 'short' });
const dayNumber = date.toLocaleDateString(locale, { day: 'numeric' });
const month = date.toLocaleDateString(locale, { month: 'short' });
const hour = date.toLocaleTimeString(locale, { hour: 'numeric', minute: 'numeric' });
React.useEffect(() => {
const interval = setInterval(() => {
setDate(new Date());
}, 60000);
return () => clearInterval(interval);
}, []);
return (
<div className={styles.dateAndHour}>
<span>{day}</span>
<span>{dayNumber}</span>
<span>{month}</span>
<span>{hour}</span>
</div>
);
};

El servidor renderiza la hora para su reloj y zona horaria; un segundo después el navegador renderiza la suya —el texto difiere y React lanza el #425—.

Arreglo 1 — suppressHydrationWarning para diferencias de texto legítimas

Cuando la diferencia es real y esperada (un reloj, un "hace 2 minutos"), dile a React que la acepte en ese elemento con suppressHydrationWarning. Solo silencia una diferencia de texto un nivel de profundidad: es un bisturí, no una manta, así que no lo esparzas por todas partes.

DateAndHour.tsx

const DateAndHour = () => {
const { locale } = useRouter();
const [date, setDate] = React.useState(new Date());
const day = date.toLocaleDateString(locale, { weekday: 'short' });
const dayNumber = date.toLocaleDateString(locale, { day: 'numeric' });
const month = date.toLocaleDateString(locale, { month: 'short' });
const hour = date.toLocaleTimeString(locale, { hour: 'numeric', minute: 'numeric' });
React.useEffect(() => {
const interval = setInterval(() => setDate(new Date()), 60000);
return () => clearInterval(interval);
}, []);
return (
<div className={styles.dateAndHour}>
<span suppressHydrationWarning>{day}</span>
<span suppressHydrationWarning>{dayNumber}</span>
<span suppressHydrationWarning>{month}</span>
<span suppressHydrationWarning>{hour}</span>
</div>
);
};

Arreglo 2 — Renderiza los valores del navegador tras el montaje

Si el valor sencillamente no existe en el servidor (window, localStorage, matchMedia), renderiza primero un marcador estable y sustitúyelo por el valor real después de que el componente se monte. El servidor y el primer render del cliente ya coinciden, y el valor del navegador aparece en el siguiente pintado.

useMounted.tsx

// Renderiza lo mismo en el servidor y en el primer render del cliente,
// y revela el valor solo-navegador una vez que sabemos que estamos en el cliente.
const useMounted = () => {
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => setMounted(true), []);
return mounted;
};
const Theme = () => {
const mounted = useMounted();
if (!mounted) return null; // misma salida en servidor y primer render del cliente
return <span>{localStorage.getItem('theme')}</span>;
};

Arreglo 3 — Carga los componentes solo-cliente con next/dynamic

Cuando un componente entero no puede renderizarse nunca en el servidor (una gráfica que lee window, un mapa, un editor), sáltate el SSR por completo en vez de proteger cada línea:

dynamic.tsx

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

Arreglo 4 — Corrige el anidamiento HTML inválido

Este sorprende porque no hay ninguna fecha ni valor aleatorio a la vista. Si anidas un elemento de bloque dentro de un <p> (o un <a> dentro de un <a>, o un <div> dentro de una <table> sin <tbody>), el navegador repara el HTML en silencio al parsearlo. React hidrata entonces contra un DOM que ya no coincide con lo que renderizó, y aparece el #425. No hay atributo que silencie esto: haz que el HTML sea válido (un <p> solo puede contener contenido en línea).

Cómo ver el error real

Nunca depures el #425 desde el número minificado. Reprodúcelo donde React sí te habla:

  • Next.js: ejecuta next dev —el desarrollo nunca está minificado—.
  • Build de producción en local: next build && next start, pero lee el mensaje en la consola del navegador, donde React imprime el texto completo y un diff del elemento que no cuadra.

React registra algo como "Warning: Text content did not match. Server: '14:32' Client: '14:33'", señalando el nodo exacto. A partir de ahí la causa suele ser obvia.

#418, #422, #423, #425 — la familia de errores de hidratación

Si tus búsquedas trajeron números vecinos, son el mismo problema visto desde ángulos distintos:

ErrorSignificado
#418La hidratación falló porque la UI inicial difería
#422La hidratación terminó pero contenía discrepancias
#423Error al hidratar; el árbol cayó a renderizado en cliente
#425El contenido de texto no coincide con el HTML del servidor

El arreglo es siempre la misma disciplina: ejecuta una build de desarrollo, encuentra el valor que difiere entre servidor y cliente, y reconcílialo con uno de los cuatro arreglos de arriba.

Puedes leer más sobre el atributo en la documentación de React.