DEV Community

Erick Eduardo Ramos
Erick Eduardo Ramos

Posted on

Cómo solucionar el error \"Text content does not match server-rendered HTML\" en Next.js

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-detection en 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>;
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

✅ 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>;
}
Enter fullscreen mode Exit fullscreen mode

✅ 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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

⚠️ 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

  1. Activa el modo estricto en desarrollo:
   NEXT_STRICT_MODE=1 next dev
Enter fullscreen mode Exit fullscreen mode

Esto activa React.StrictMode y muestra más detalles sobre la mismatch.

  1. Busca en el DOM el elemento que causa el error:

    • Abre DevTools → Network tab → Desactiva "Disable cache"
    • Recarga con Ctrl+Shift+R (o Cmd+Shift+R)
    • Busca en el HTML inicial (sin JS) y compáralo con el DOM renderizado
  2. Verifica tu CDN/Edge:

    • Desactiva temporalmente Cloudflare Auto Minify o cualquier optimización HTML
    • Si el error desaparece, configura reglas para excluir /app del 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)