Cómo solucionar el error "Text content does not match server-rendered HTML" en Next.js
Este error ocurre cuando el HTML generado en el servidor (SSR/SSG) no coincide con lo que React intenta hidratar en el navegador. Es un problema crítico que rompe la hidratación y puede dejar la aplicación inestable o no funcional.
Causa raíz
En tu caso, el error está relacionado con contenidos dinámicos que cambian entre renderizado del servidor y renderizado inicial en cliente, especialmente:
- Uso de
Date()o APIs de tiempo real (como fechas/horas dinámicas) - Uso de
typeof window !== 'undefined'directamente en el render - Metaetiquetas que modifican el DOM (como
format-detectionen iOS) - Extensiones del navegador que inyectan elementos
- Configuraciones de CDN (ej. Cloudflare Auto Minify) que alteran el HTML
Dado el contexto de tu código (NEXT.JS NIGHTSSF JUN 9 • AMS JUN 11 • LDN JUN 18), lo más probable es que estés generando dinámicamente fechas/horas en el render, o usando new Date() en el render del componente.
Solución definitiva (3 pasos)
✅ Paso 1: Evita lógica dependiente del cliente en el render
NO hagas esto:
// ❌ Mala práctica: Date() en render → cambia entre SSR y CSR
export default function EventCard({ date }) {
const now = new Date(); // ← Esto cambia en cada render
return <span>{date} • {now.toLocaleTimeString()}</span>;
}
Haz esto en su lugar:
// ✅ Correcto: Usa solo props o datos estáticos en el render
export default function EventCard({ date, timestamp }) {
return (
<time dateTime={timestamp} suppressHydrationWarning>
{date}
</time>
);
}
✅ Paso 2: Usa suppressHydrationWarning para contenido intencionalmente dinámico
Si necesitas mostrar la fecha/hora actual (ej. "Hace 2 minutos"), no la calcules en el render. Usa useEffect para actualizarla después de la hidratación:
// ✅ Correcto: Actualizar tiempo tras hidratación
import { useState, useEffect } from 'react';
export default function RelativeTime({ timestamp }) {
const [timeAgo, setTimeAgo] = useState('');
useEffect(() => {
const update = () => {
const diff = Date.now() - new Date(timestamp).getTime();
// Lógica para formatear "hace X min"
setTimeAgo(`${Math.round(diff / 60000)} min atrás`);
};
update();
const interval = setInterval(update, 60000);
return () => clearInterval(interval);
}, [timestamp]);
return <span suppressHydrationWarning>{timeAgo}</span>;
}
✅ Paso 3: Deshabilita detección automática en iOS (si aplica)
Añade esta metaetiqueta en <head> de tu layout.tsx o document.tsx:
// app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="es">
<head>
<meta
name="format-detection"
content="telephone=no, date=no, email=no, address=no"
/>
</head>
<body>{children}</body>
</html>
);
}
Bloque de código corregido (ejemplo práctico)
// app/events/EventList.tsx
import { useState, useEffect } from 'react';
export default function EventList({ events }) {
return (
<ul>
{events.map((event) => (
<li key={event.id}>
<EventCard {...event} />
</li>
))}
</ul>
);
}
function EventCard({ name, date, timestamp }) {
const [isClient, setIsClient] = useState(false);
useEffect(() => {
setIsClient(true);
}, []);
return (
<div>
<span>{name}</span>
<time dateTime={timestamp} suppressHydrationWarning>
{isClient ? 'Cargado en cliente' : date} {/* ← Mismo contenido SSR/CSR */}
</time>
</div>
);
}
⚠️ Nota crítica: Si usas
suppressHydrationWarning, asegúrate de que el contenido dinámico solo cambie después de la hidratación. Nunca lo uses para ocultar errores reales de estructura HTML.
Pro-tip: Diagnóstico rápido
- Activa el modo estricto en desarrollo:
NEXT_STRICT_MODE=1 next dev
Esto activa React.StrictMode y muestra más detalles sobre la mismatch.
-
Busca en el DOM el elemento que causa el error:
- Abre DevTools → Network tab → Desactiva "Disable cache"
- Recarga con
Ctrl+Shift+R(oCmd+Shift+R) - Busca en el HTML inicial (sin JS) y compáralo con el DOM renderizado
-
Verifica tu CDN/Edge:
- Desactiva temporalmente Cloudflare Auto Minify o cualquier optimización HTML
- Si el error desaparece, configura reglas para excluir
/appdel minificado
¡Listo! Con esto eliminarás el error de forma definitiva. Si persiste, revisa si tienes extensiones como "Dark Reader" o "React Developer Tools" inyectando nodos en el DOM.
Top comments (0)