DEV Community

Erick Eduardo Ramos
Erick Eduardo Ramos

Posted on

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

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

Este error ocurre cuando el HTML generado en el servidor no coincide con el que React genera durante la hidratación inicial en el cliente. Es un problema crítico de consistencia de estado que rompe la hidratación y puede causar comportamientos impredecibles o UI rota.

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, probablemente causado por:

  • Uso de Date() o new Date() en el renderizado (ej. mostrar la hora actual, fechas relativas como "hace 5 minutos")
  • Uso de APIs del navegador (window, localStorage, navigator) directamente en el render
  • Metaetiquetas o scripts que modifican el DOM antes de la hidratación (como iOS detectando números telefónicos y convirtiéndolos en enlaces)
  • Extensiones del navegador inyectando contenido
  • Configuraciones de CDN (ej. Cloudflare Auto Minify) que alteran el HTML prerrendereado

Solución definitiva (pasos verificados)

✅ Paso 1: Identificar el elemento problemático

Busca en tu código componentes que:

  • Impriman fechas/horas actuales (new Date(), Date.now())
  • Usen localStorage.getItem(), window.innerWidth, etc.
  • Rendericen contenido condicional basado en typeof window !== 'undefined'

⚠️ Ejemplo común problemático:

// ❌ MAL: Rompe SSR porque `new Date()` da distinto valor en servidor vs cliente
<footer>Última actualización: {new Date().toLocaleString()}</footer>

✅ Paso 2: Aplicar una de estas correcciones según el caso

🔹 Caso A: Contenido que debe variar (ej. timestamp)

Usa suppressHydrationWarning en el elemento específico:

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

Pro-tip: Solo aplica esto a elementos textuales y de bajo impacto visual. Nunca en contenedores estructurales.

🔹 Caso B: Lógica dependiente del cliente (ej. localStorage)

Usa useEffect para retrasar la renderización:

'use client'

import { useState, useEffect } from 'react'

export default function UserGreeting() {
  const [user, setUser] = useState<string | null>(null)
  const [isClient, setIsClient] = useState(false)

  useEffect(() => {
    const storedUser = localStorage.getItem('user')
    setUser(storedUser)
    setIsClient(true)
  }, [])

  // Renderiza placeholder durante SSR y primera hidratación
  if (!isClient) return <span>Cargando...</span>

  return (
    <div>
      {user ? `Hola, ${user}` : 'Invitado'}
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

🔹 Caso C: iOS detecta números como teléfonos

Agrega la 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

🔹 Caso D: Componente complejo con múltiples dependencias del cliente

Deshabilita SSR para ese componente específico:

// components/DynamicComponent.tsx
export default function DynamicComponent() {
  // Aquí puedes usar window, localStorage, etc.
  const [data, setData] = useState(null)

  useEffect(() => {
    setData(window.innerWidth)
  }, [])

  return <div>Ancho: {data}</div>
}

// En tu página
'use client'
import dynamic from 'next/dynamic'

const DynamicComponentNoSSR = dynamic(() => import('../components/DynamicComponent'), {
  ssr: false,
})

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

✅ Paso 3: Verifica configuraciones de infraestructura

  • Cloudflare: Desactiva Auto Minify en Speed > Optimization
  • Otros CDN: Revisa si hay HTML rewriting o edge functions que modifiquen el HTML
  • Extensiones de navegador: Prueba en modo incógnito para descartar interferencia

Pro-tip: Diagnóstico rápido

  1. Abre DevTools → Network tab → Desactiva Disable cache
  2. Recarga con Disable JavaScript activado → Si el HTML se ve correcto, el problema está en JS
  3. Usa console.log('client', window) dentro de un componente → Si aparece en SSR, estás usando APIs del cliente en el render

🔥 Regla de oro: Todo lo que se renderice en el servidor debe producir el mismo HTML que el primer render del cliente. Si no puedes garantizarlo, usa suppressHydrationWarning o dynamic({ ssr: false }).

Aplica estos pasos y el error desaparecerá de forma permanente.

Top comments (0)