Next.js: Solucionar 'Hydration failed because the initial UI does not match'
Si has encontrado el temido error “Hydration failed because the initial UI does not match what was rendered on the server” en tu aplicación Next.js, no estás solo. Este problema de discrepancia en la hidratación de React es uno de los más comunes que enfrentan los desarrolladores al construir aplicaciones renderizadas del lado del servidor. Vamos a aclarar la confusión y solucionarlo correctamente.
Puntos Clave
- Los errores de hidratación ocurren cuando el servidor y el cliente renderizan HTML diferente
- Las APIs exclusivas del navegador, valores aleatorios y anidación HTML inválida son culpables comunes
- Usa useEffect para lógica del lado del cliente, importaciones dinámicas para componentes exclusivos del cliente, o asegura renderizado determinístico
- Mantén los renderizados iniciales idénticos entre servidor y cliente
¿Qué Es la Hidratación y Por Qué Falla?
La hidratación es el proceso de React para adjuntar funcionalidad JavaScript al HTML renderizado en el servidor. Cuando Next.js envía HTML pre-renderizado al navegador, React toma el control comparando el HTML del servidor con lo que renderizaría en el cliente. Si no coinciden, obtienes un error de hidratación.
Piénsalo como si React estuviera verificando el trabajo del servidor. Cuando los renderizados iniciales difieren, React no puede adjuntar de forma segura los manejadores de eventos y la gestión de estado, lo que desencadena la necesidad de una corrección de hidratación.
Causas Comunes de Errores de Hidratación en Next.js
APIs Exclusivas del Navegador Rompiendo SSR
El culpable más frecuente en problemas de renderizado del lado del servidor en React es usar APIs del navegador durante el renderizado inicial:
// ❌ Esto falla - window no existe en el servidor
function BadComponent() {
const width = window.innerWidth;
return <div>Screen width: {width}px</div>;
}
Valores No Determinísticos
Valores aleatorios o marcas de tiempo crean salidas diferentes entre servidor y cliente:
// ❌ El servidor y el cliente generarán IDs diferentes
function RandomComponent() {
return <div id={Math.random()}>Content</div>;
}
Diferencias en Renderizado Condicional
Cuando tu lógica produce diferentes estructuras HTML:
// ❌ El estado mounted difiere entre servidor (false) y cliente (true después del effect)
function ConditionalComponent() {
const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);
return <div>{mounted ? 'Client' : 'Server'}</div>;
}
Anidación HTML Inválida
Estructura HTML incorrecta que los navegadores auto-corrigen:
<!-- ❌ Anidación inválida -->
<p>
<div>This breaks hydration</div>
</p>
Discover how at OpenReplay.com.
Tres Soluciones Confiables para Errores de Hidratación
Solución 1: Envolver Lógica Exclusiva del Cliente con useEffect
El hook useEffect se ejecuta después de que la hidratación se completa, haciéndolo seguro para código específico del navegador:
function SafeComponent() {
const [screenWidth, setScreenWidth] = useState(0);
useEffect(() => {
// Esto solo se ejecuta en el cliente después de la hidratación
setScreenWidth(window.innerWidth);
}, []);
// Retorna un renderizado inicial consistente
if (screenWidth === 0) return <div>Loading...</div>;
return <div>Screen width: {screenWidth}px</div>;
}
Solución 2: Deshabilitar SSR con Importaciones Dinámicas
Para componentes que dependen fuertemente de APIs del navegador, omite completamente el renderizado del servidor:
import dynamic from 'next/dynamic';
const ClientOnlyComponent = dynamic(
() => import('./BrowserComponent'),
{
ssr: false,
loading: () => <div>Loading...</div> // Previene cambios de diseño
}
);
export default function Page() {
return <ClientOnlyComponent />;
}
Solución 3: Asegurar Renderizado Determinístico
Genera valores estables que permanezcan consistentes entre renderizados:
// ✅ Usa IDs estables desde props o generados una vez
function StableComponent({ userId }) {
// Usa un ID determinístico basado en props
const componentId = `user-${userId}`;
return <div id={componentId}>Consistent content</div>;
}
// Para valores verdaderamente aleatorios, genera en el servidor
export async function getServerSideProps() {
return {
props: {
sessionId: generateStableId() // Genera una vez en el servidor
}
};
}
Depurando Problemas de SSR en Next.js
Al abordar la depuración de SSR en Next.js, usa estas técnicas:
- Habilita las advertencias estrictas de hidratación de React en desarrollo
- Compara el HTML del servidor y del cliente usando las DevTools del navegador
- Agrega console logs para identificar qué componente causa la discrepancia
- Usa React DevTools para inspeccionar el árbol de componentes
// Ayudante temporal de depuración
useEffect(() => {
console.log('Component hydrated:', typeof window !== 'undefined');
}, []);
Mejores Prácticas para Prevenir Futuros Errores de Hidratación
Mantén los renderizados iniciales idénticos: El servidor y el cliente deben producir el mismo HTML en el primer renderizado. Guarda las actualizaciones dinámicas para después de la hidratación.
Valida la estructura HTML: Usa anidación apropiada y elementos HTML válidos. Herramientas como el Validador W3C pueden detectar problemas tempranamente.
Prueba con JavaScript deshabilitado: Tu contenido renderizado en el servidor debe ser funcional sin JavaScript, asegurando una base sólida.
Usa TypeScript: La verificación de tipos ayuda a detectar posibles discrepancias durante el desarrollo en lugar de en tiempo de ejecución.
Conclusión
Los errores de discrepancia de hidratación de React en Next.js son frustrantes pero predecibles una vez que entiendes el patrón. La clave es asegurar que tu servidor y cliente produzcan HTML inicial idéntico. Ya sea que uses useEffect para lógica del lado del cliente, deshabilites SSR con importaciones dinámicas, o asegures renderizado determinístico, la solución siempre vuelve a este principio: mantén el primer renderizado consistente, luego agrega características del lado del cliente después.
Recuerda: si tu código depende del entorno del navegador, mantenlo fuera de la ruta de renderizado inicial. Tus aplicaciones Next.js se hidratarán sin problemas, y tus usuarios nunca conocerán la complejidad que ocurre detrás de escena.
Preguntas Frecuentes
Sí, pero no es recomendable. Las advertencias de hidratación indican problemas reales que pueden causar inconsistencias en la UI. En lugar de suprimirlas, soluciona la causa raíz usando useEffect o importaciones dinámicas para asegurar la funcionalidad adecuada.
Las fechas a menudo se renderizan de manera diferente en servidor y cliente debido a diferencias de zona horaria. Usa un formato consistente convirtiendo las fechas a strings en el servidor o usa una librería como date-fns con zonas horarias fijas para ambos entornos.
Carga scripts de terceros después de la hidratación usando el componente Script de Next.js con strategy afterInteractive o lazyOnload. Para componentes que dependen de estos scripts, envuélvelos en importaciones dinámicas con ssr false.
Deshabilitar SSR significa que esos componentes no aparecerán en el HTML inicial, potencialmente afectando el SEO y aumentando el tiempo hasta la interactividad. Úsalo con moderación para características verdaderamente dependientes del cliente y proporciona estados de carga para prevenir cambios de diseño.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.