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 App Router

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

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

La discrepancia suele provenir de:

  • Uso de APIs del navegador (window, localStorage, Date.now()) durante el renderizado.
  • Lógica condicional que depende del entorno (typeof window !== 'undefined') en el body del componente (no dentro de useEffect).
  • Extensiones del navegador que inyectan elementos (como ad-blockers o traductores).
  • Metaetiquetas de detección automática en iOS (format-detection) que modifican el DOM.
  • Minificación automática por CDN (Cloudflare Auto Minify, Vercel Edge Config mal configurado).

Solución definitiva (pasos verificados)

✅ Paso 1: Localiza la fuente exacta del mismatch

  1. Abre la consola del navegador.
  2. Haz clic en el error → ve a la pestaña "Sources" → busca el archivo donde ocurre.
  3. Busca líneas como:
   <div>Prerendered: {someClientOnlyValue}</div>
Enter fullscreen mode Exit fullscreen mode

o

   <span>{new Date().toLocaleString()}</span>
Enter fullscreen mode Exit fullscreen mode

✅ Paso 2: Aplica la solución según el caso

🔹 Caso A: Contenido dinámico (fechas, IDs aleatorios, etc.)

Usa suppressHydrationWarning en el elemento específico:

<time suppressHydrationWarning>{new Date().toISOString()}</time>
Enter fullscreen mode Exit fullscreen mode

⚠️ No lo uses en contenedores grandes (ej. <div suppressHydrationWarning>...</div>), solo en nodos atómicos.

🔹 Caso B: Lógica condicional basada en window

Mueve la lógica client-side a useEffect + estado local:

'use client';

import { useState, useEffect } from 'react';

export default function ClientComponent() {
  const [isClient, setIsClient] = useState(false);
  const [time, setTime] = useState('');

  useEffect(() => {
    setIsClient(true);
    setTime(new Date().toLocaleTimeString());
  }, []);

  return (
    <div>
      {/* Server-safe render */}
      {!isClient && <span>Cargando...</span>}
      {/* Client-only render */}
      {isClient && <span>Hora actual: {time}</span>}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

🔹 Caso C: Componentes que usan localStorage/window

Desactiva SSR para ese componente:

// components/ClientOnly.tsx
'use client';

import dynamic from 'next/dynamic';

const ClientOnly = dynamic(() => import('./ClientOnlyComponent'), {
  ssr: false,
});

export default function Page() {
  return (
    <div>
      <ClientOnly />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

🔹 Caso D: iOS detecta automáticamente enlaces en fechas/teléfonos

Agrega esta metaetiqueta en app/layout.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

Pro-tip: Prevención proactiva

  1. Nunca uses APIs del navegador fuera de useEffect o useLayoutEffect.
  2. Evita Math.random() o Date() en el renderizado directo.
  3. Usa next/dynamic con ssr: false para librerías client-only (ej. react-chartjs-2, framer-motion).
  4. Desactiva minificación automática en Cloudflare (si usas Auto Minify, desactívalo para /app).
  5. Valida con next build && next start (no con next dev, que omite SSR).

💡 Debug rápido: Ejecuta next build localmente y revisa los logs de "Hydration failed". Te dirá exactamente qué línea y componente falla.


Bloque de código corregido (ejemplo real)

❌ Antes (error):

export default function Timestamp() {
  return <p>Hoy es {new Date().toLocaleDateString()}</p>;
}
Enter fullscreen mode Exit fullscreen mode

✅ Después (correcto):

'use client';

import { useState, useEffect } from 'react';

export default function Timestamp() {
  const [date, setDate] = useState('');

  useEffect(() => {
    setDate(new Date().toLocaleDateString());
  }, []);

  return <p suppressHydrationWarning>Hoy es {date}</p>;
}
Enter fullscreen mode Exit fullscreen mode

Con esto, el error desaparecerá definitivamente.

Top comments (0)