Cómo solucionar el Hydration Error de Next.js [Guía completa 2026]
¿Estás viendo de repente “Hydration failed because the initial UI does not match what was rendered on the server” o “Text content does not match server-rendered HTML” mientras desarrollas con Next.js? Este artículo ofrece una guía completa actualizada a 2026, cubriendo las causas y soluciones concretas para los errores de hidratación, incluyendo el entorno más reciente de Next.js 15.
- ¿Qué es este error? Síntomas que verás
- ¿Por qué ocurre este error?
- Solución 1: Renderizado exclusivo del cliente con useEffect (Recomendada)
- Solución 2: Desactivar SSR con importación dinámica
- Solución 3: Corregir la estructura HTML y manejar extensiones del navegador (Avanzado)
- Cómo prevenir errores de hidratación
- Resumen
- Referencias
¿Qué es este error? Síntomas que verás
El Hydration Error (error de hidratación) de Next.js ocurre cuando hay una discrepancia entre el HTML generado por el renderizado del lado del servidor (SSR) y el HTML que React intenta renderizar en el primer renderizado del lado del cliente.
¿Qué es la hidratación?
La hidratación (Hydration) es el proceso en el que React toma el HTML estático prerenderizado del servidor y le adjunta manejadores de eventos y estado para hacerlo “interactivo” en el navegador. Se llama “hidratación” porque inyecta interactividad, como agregar agua para dar vida a algo.
Mensajes de error comunes
Estos son los mensajes de error típicos que los desarrolladores encuentran en la consola o el navegador:
Hydration failed because the initial UI does not match what was rendered on the server.Text content does not match server-rendered HTML.Error: Hydration failed because the server rendered HTML didn't match the client.Warning: Expected server HTML to contain a matching <div> in <p>.
¿Cuándo ocurre?
Este error aparece principalmente en las siguientes situaciones:
- Aparece un error rojo en la consola del navegador durante el desarrollo de la aplicación Next.js
- Partes de una página no se renderizan correctamente o las interacciones no funcionan en producción
- A menudo se detecta por primera vez durante la vista previa posterior al build
- Las extensiones de Chrome que modifican el DOM también pueden desencadenarlo
En cuanto al impacto, cuando ocurre un error de hidratación, React intenta reconstruir completamente el DOM en el lado del cliente, lo que puede degradar el rendimiento. Además, en Next.js 15 y versiones posteriores (React 19), los errores de hidratación se manejan de manera más estricta: lo que antes eran advertencias ahora pueden lanzarse como errores.
¿Por qué ocurre este error?
Los errores de hidratación tienen muchas causas, pero se pueden clasificar en las siguientes cinco categorías.
Causa 1: Uso de APIs exclusivas del navegador (window, localStorage, navigator)
Como el entorno del navegador no existe en el lado del servidor, hacer referencia directa a objetos exclusivos del navegador como window, localStorage, navigator o document en la lógica de renderizado produce diferentes resultados en el servidor y el cliente.
// ❌ MAL: window no existe en el servidor, causando discrepancia
function MyComponent() {
const width = window.innerWidth; // Error o undefined en el servidor
return <div>{width > 768 ? 'Escritorio' : 'Móvil'}</div>;
}
Un patrón típico es usar la ramificación condicional typeof window !== 'undefined' en el renderizado, lo que devuelve JSX diferente en el servidor y el cliente, provocando el error.
Causa 2: Uso de fechas, horas y valores aleatorios
Funciones que devuelven valores diferentes en cada llamada, como new Date(), Date.now(), Math.random() y crypto.randomUUID(), producen valores no coincidentes entre servidor y cliente cuando se usan durante el renderizado.
// ❌ MAL: Se muestran horas diferentes en servidor y cliente
function Clock() {
return <p>Hora actual: {new Date().toLocaleTimeString()}</p>;
}
Las diferencias de zona horaria también son una causa común. Cuando el servidor funciona en UTC y el cliente está en una zona horaria local, el mismo objeto Date genera cadenas diferentes.
Causa 3: Anidamiento HTML inválido
Cuando la estructura HTML viola las reglas de anidamiento (por ejemplo, colocar un <div> dentro de un <p>), el navegador autocorrige el HTML, creando una discrepancia entre el HTML enviado por el servidor y el DOM interpretado por el navegador.
// ❌ MAL: <div> no puede ir dentro de <p>
function BadNesting() {
return (
<p>
Texto
<div>Esta parte es el problema</div>
</p>
);
}
Causa 4: Extensiones de Chrome que modifican el DOM
En 2026, una de las causas más frustrantes para muchos desarrolladores son las extensiones de Chrome. Extensiones como ColorZilla, Grammarly, Google Translate y gestores de contraseñas agregan atributos o elementos al DOM antes de que comience la hidratación de React, causando discrepancias con el HTML del servidor.
Especialmente en entornos Next.js 15 + React 19, atributos inyectados por extensiones como cz-shortcut-listen="true" se han reportado como causas directas de errores de hidratación.
Causa 5: Scripts de terceros e interferencia de widgets
Scripts de terceros como Google Analytics, herramientas de pruebas A/B, widgets de chat y scripts de publicidad pueden modificar el DOM antes de la hidratación de React, provocando errores.
Solución 1: Renderizado exclusivo del cliente con useEffect (Recomendada)
La solución más recomendada es usar el hook useEffect para establecer valores solo en el lado del cliente. Como useEffect se ejecuta después de que la hidratación se complete, mantiene la consistencia del HTML entre servidor y cliente.
Paso 1: Patrón de combinación state y useEffect
Cuando necesitas valores específicos del navegador (ancho de pantalla, valores de localStorage, etc.), establece el valor inicial para que coincida con el servidor, luego actualiza a valores específicos del cliente dentro de useEffect.
'use client';
import { useState, useEffect } from 'react';
function ResponsiveComponent() {
// Paso 1: Establecer valor inicial igual al del servidor
const [isMobile, setIsMobile] = useState(false);
// Paso 2: Establecer valor específico del cliente en useEffect
useEffect(() => {
setIsMobile(window.innerWidth < 768);
const handleResize = () => setIsMobile(window.innerWidth < 768);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return (
<div>
{isMobile ? <MobileNav /> : <DesktopNav />}
</div>
);
}
Paso 2: Patrón de seguimiento del estado de montaje
Un patrón más versátil rastrea si el componente se ha montado en el lado del cliente.
'use client';
import { useState, useEffect } from 'react';
function ClientOnlyComponent() {
const [isMounted, setIsMounted] = useState(false);
useEffect(() => {
setIsMounted(true);
}, []);
if (!isMounted) {
return <div className="skeleton">Cargando...</div>;
}
return (
<div>
<p>Hora actual: {new Date().toLocaleTimeString()}</p>
<p>Navegador: {navigator.userAgent}</p>
</div>
);
}
Paso 3: Crear un Hook personalizado reutilizable
Recomendamos crear un hook personalizado para reutilizar en todo el proyecto.
// hooks/useHydration.ts
'use client';
import { useState, useEffect } from 'react';
export function useHydrated() {
const [hydrated, setHydrated] = useState(false);
useEffect(() => {
setHydrated(true);
}, []);
return hydrated;
}
// Uso
function MyComponent() {
const hydrated = useHydrated();
if (!hydrated) return <Skeleton />;
return <div>{/* Contenido específico del cliente */}</div>;
}
Notas importantes
- Es crucial mantener la misma salida que el servidor durante el renderizado inicial de
useEffect - Usa UI de esqueleto o indicadores de carga para minimizar la degradación de la experiencia del usuario
- Este enfoque puede afectar el SEO, así que aplícalo con cuidado al contenido que quieras que los motores de búsqueda indexen
Solución 2: Desactivar SSR con importación dinámica
Cuando el patrón useEffect resulta inconveniente o cuando todo el componente es solo del cliente, puedes desactivar SSR usando dynamic() de Next.js.
Uso básico
import dynamic from 'next/dynamic';
const ClientOnlyChart = dynamic(
() => import('../components/Chart'),
{
ssr: false,
loading: () => <p>Cargando gráfico...</p>
}
);
export default function Dashboard() {
return (
<div>
<h1>Panel de control</h1>
<ClientOnlyChart />
</div>
);
}
Uso con App Router
Con Next.js 13+ App Router, dynamic funciona de la misma manera.
// app/dashboard/page.tsx
import dynamic from 'next/dynamic';
const MapComponent = dynamic(
() => import('@/components/Map'),
{ ssr: false }
);
export default function DashboardPage() {
return (
<main>
<h1>Vista de mapa</h1>
<MapComponent />
</main>
);
}
Cuándo es apropiado este enfoque
- Componentes que usan bibliotecas de mapas (Leaflet, Google Maps)
- Componentes que usan bibliotecas de gráficos (Chart.js, D3.js)
- Componentes muy dependientes de APIs del navegador
- Componentes de editor (Monaco Editor, CodeMirror, etc.)
Solución 3: Corregir la estructura HTML y manejar extensiones del navegador (Avanzado)
Corregir problemas de anidamiento HTML
Si la causa es un anidamiento HTML inválido, corrige la estructura para cumplir con las especificaciones HTML.
// ❌ MAL: <div> no puede ir dentro de <p>
<p>Texto<div>Elemento de bloque</div></p>
// ✅ BIEN: Envolver con <div> o usar <span>
<div>
<p>Texto</p>
<div>Elemento de bloque</div>
</div>
// ✅ BIEN: Usar elementos en línea
<p>Texto<span>Elemento en línea</span></p>
Manejar extensiones de Chrome
Método A: Restringe el acceso de la extensión al sitio. Haz clic derecho en el icono de la extensión de Chrome y cambia “Leer y cambiar datos del sitio” a “Cuando hagas clic en la extensión”.
Método B: Crea un perfil de navegador específico para desarrollo sin extensiones.
Método C: Usa suppressHydrationWarning selectivamente.
// app/layout.tsx
export default function RootLayout({ children }) {
return (
<html lang="es">
<body suppressHydrationWarning={true}>
{children}
</body>
</html>
);
}
Cómo prevenir errores de hidratación
1. Evita código dependiente del entorno durante el renderizado
No uses código que genere valores diferentes según el entorno en la sentencia return del componente. Mueve toda la lógica dependiente del entorno a useEffect.
2. Aprovecha las CSS Media Queries
Para el cambio de diseño responsivo, las CSS Media Queries son el enfoque más seguro.
.mobile-only { display: none; }
.desktop-only { display: block; }
@media (max-width: 768px) {
.mobile-only { display: block; }
.desktop-only { display: none; }
}
3. Automatiza la validación HTML
Usa el plugin jsx-a11y de ESLint y las reglas de eslint-plugin-react para detectar anidamientos HTML inválidos en tiempo de compilación.
4. Establece un entorno de pruebas
- Prueba regularmente con
next build && next start - Prueba con un perfil de navegador limpio sin extensiones
- Monitorea errores de hidratación en producción con herramientas como Sentry
5. Optimiza la ubicación de scripts de terceros
import Script from 'next/script';
<Script src="https://example.com/analytics.js" strategy="afterInteractive" />
<Script src="https://example.com/widget.js" strategy="lazyOnload" />
Resumen
El Hydration Error de Next.js es causado por discrepancias entre los resultados de renderizado del servidor y del cliente. En el entorno de Next.js 15 + React 19 de 2026, estas se verifican más estrictamente que antes, haciendo que la comprensión y el manejo adecuados sean cada vez más importantes.
Puntos clave:
- El patrón useEffect es la solución más efectiva para la mayoría de los casos
- Dynamic import (ssr: false) debe usarse cuando todo el componente depende del cliente
- Siempre cuida la estructura HTML correcta
- Para problemas con extensiones de Chrome, usa un perfil de desarrollo o
suppressHydrationWarning - Prioriza CSS Media Queries sobre la ramificación condicional en JavaScript
Referencias
- Documentación oficial de Next.js: Text content does not match server-rendered HTML
- GitHub Discussion: Hydration failed because the initial UI does not match
- GitHub Discussion: Hydration Error caused by chrome extension
- Next.js Hydration Errors in 2026 (Medium)
- Sentry: Fixing Hydration Errors in server-rendered Components
- Resolving hydration mismatch errors in Next.js – LogRocket Blog

コメント