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 el árbol de React generado durante la primera renderización en el navegador (hydration). React espera que el DOM inicial coincida exactamente con el que se renderizó en el servidor; cualquier diferencia rompe la hidratación y causa este error crítico.

Causa raíz

En tu caso, el problema está relacionado con contenido dinámico que varía entre renderizado del servidor y renderizado del cliente, como:

  • Uso de Date.now(), new Date(), Math.random() o localStorage en el renderizado directo.
  • Uso de typeof window !== 'undefined' dentro de la lógica de renderizado (no dentro de useEffect).
  • Metaetiquetas de detección automática de iOS (format-detection) que inyectan nodos <a> en el DOM del cliente.
  • Extensiones del navegador que modifican el HTML (ej. traductores, bloqueadores de anuncios).
  • Configuraciones de CDN como Cloudflare Auto Minify que alteran el HTML prerrendereado.

Pasos para solucionarlo (ordenados por prioridad)

1. Identifica y elimina lógica no determinista en el renderizado

❌ Incorrecto:

// ❌ ¡Esto rompe la hidratación!
export default function Timestamp() {
  return <span>{new Date().toISOString()}</span>
}
Enter fullscreen mode Exit fullscreen mode

✅ Correcto (opción 1: usar useEffect para diferir el renderizado dinámico):

import { useState, useEffect } from 'react'

export default function Timestamp() {
  const [time, setTime] = useState<string>('...')

  useEffect(() => {
    setTime(new Date().toISOString())
  }, [])

  return <span>{time}</span>
}
Enter fullscreen mode Exit fullscreen mode

✅ Correcto (opción 2: deshabilitar SSR para el componente problemático):

import dynamic from 'next/dynamic'

const DynamicTimestamp = dynamic(() => import('./Timestamp'), { ssr: false })

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

2. Verifica metaetiquetas de detección automática en iOS

Agrega esta metaetiqueta en <head> (en app/layout.tsx o app/layout.tsx con next/head):

// app/layout.tsx
import { Metadata } from 'next'

export const metadata: Metadata = {
  // ...
  other: {
    'format-detection': 'telephone=no, date=no, email=no, address=no',
  },
}
Enter fullscreen mode Exit fullscreen mode

O directamente en el <head> si usas next/head:

import Head from 'next/head'

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <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

3. Usa suppressHydrationWarning solo como último recurso

Solo para casos ineludibles (ej. timestamps, IDs aleatorios):

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

⚠️ Importante: Esta solución no corrige el problema, solo silencia la advertencia. React dejará de intentar sincronizar ese nodo, lo que puede causar comportamientos inesperados si se usa abusivamente.

4. Verifica configuraciones de CDN y minificación

Si usas Cloudflare, Vercel Edge Config, o cualquier proxy intermedio:

  • Desactiva Auto Minify HTML (Cloudflare).
  • Asegúrate de que no se estén inyectando scripts o modificando el HTML prerrendereado.
  • Prueba localmente (next dev) para confirmar si el error desaparece (si sí, el problema está en el entorno de producción).

Bloque de código corregido (ejemplo completo)

// app/page.tsx
import { useState, useEffect } from 'react'

function ClientOnlyTimestamp() {
  const [time, setTime] = useState<string>('Cargando...')

  useEffect(() => {
    setTime(new Date().toLocaleString())
  }, [])

  return <time suppressHydrationWarning>{time}</time>
}

export default function Page() {
  return (
    <div>
      <h1>Última actualización:</h1>
      <ClientOnlyTimestamp />
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Pro-tip: Diagnóstico rápido

  1. Abre DevTools > Network > Disable cache.
  2. Haz Hard Reload (Ctrl+Shift+R / Cmd+Shift+R).
  3. Busca en la consola: el error muestra el primer nodo divergente (ej. Expected text "12:00" but received "12:01").
  4. Usa console.log(window?.location?.href) solo dentro de useEffect para depurar sin afectar SSR.

🔥 Regla de oro: Si algo no es idéntico entre servidor y cliente, no debe estar en el render directo. Usa useEffect, dynamic({ ssr: false }), o suppressHydrationWarning con extremo cuidado.

Top comments (0)