Logo
Empezar a leer

© 2025 - alckordev

Cómo evitar errores de hidratación con next-themes en Next.js (App Router)

5 agosto, 2025

3 minutos de lectura

0

Cómo evitar errores de hidratación con next-themes en Next.js (App Router)

Los errores de hydration (o hydration mismatch) aparecen cuando el HTML renderizado en el servidor no coincide con lo que React pinta en el cliente.
Con next-themes y App Router (Next 13/14) esto sucede porque el componente <ThemeProvider /> lee window.matchMedia, localStorage, etc. —APIs que no existen durante el SSR—.
El resultado típico es:

A continuación veremos:

  1. Por qué ocurre el error.
  2. Cómo crear un wrapper ThemeProvider sin SSR.
  3. Cómo usarlo en layout.tsx.
  4. Cómo consumir el hook useTheme() desde cualquier Client Component con un ejemplo de ThemeSwitcher.
  5. Recomendaciones finales.

1. ¿Por qué ocurre el hydration mismatch?

  • next-themes decide el tema (light/dark/system) durante el primer render.
  • En App Router, todo lo que coloques en layout.tsx se ejecuta primero en el servidor.
  • Lecturas de window y almacenamiento local no existen allí → el HTML sale con un tema “por defecto”.
  • Al hidratar, el cliente detecta que el tema real es distinto y React avisa de la discrepancia.

La solución es renderizar ThemeProvider solo en el navegador.

2. Creando un ThemeProvider compatible con App Router

src/providers/theme-provider.tsx

"use client";

import dynamic from "next/dynamic";
import { type ThemeProviderProps } from "next-themes";

// Dynamic import with SSR disabled
const NextThemesProvider = dynamic(() => import("next-themes").then((m) => m.ThemeProvider), {
  ssr: false,
});

export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
  return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
}

3. Integración en Layout

src/app/layout.tsx

import "@/styles/globals.css";
import { ThemeProvider } from "@/providers/theme-provider";

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <ThemeProvider
          attribute="class"
          defaultTheme="system"
          enableSystem
          disableTransitionOnChange
        >
          {children}
        </ThemeProvider>
      </body>
    </html>
  );
}

Ahora tu árbol React en el servidor no incluye ThemeProvider, evitando el desajuste.

4. Consumir el hook useTheme

Con el provider configurado, puedes usar useTheme() en cualquier Client Component para leer o cambiar el tema.

src/components/theme-switcher.tsx

"use client";

import { RiMoonLine, RiSunLine } from "@remixicon/react";
import { IconButton } from "./ui/icon-button";
import { useTheme } from "next-themes";

export const ThemeSwitcher = () => {
  const { setTheme, theme } = useTheme();

  return (
    <IconButton
      aria-label="Toggle Theme"
      variant="outline"
      onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
    >
      {theme === "dark" ? <RiSunLine /> : <RiMoonLine />}
    </IconButton>
  );
};

Coloca <ThemeSwitcher /> en tu navbar o en cualquier parte del cliente y funcionará sin parpadeos ni warnings.

5. Recomendaciones y buenas prácticas

Buenas prácticasExplicación
suppressHydrationWarning en <html>Evita warnings de React cuando cambie la clase de tema
disableTransitionOnChangeQuita flickering si usas Tailwind o CSS con transiciones
Persistir preferenciaEl propio next-themes guarda el tema en localStorage; si prefieres cookies, lee useTheme() y persiste manualmente
TailwindAsegúrate de darkMode: "class" en tailwind.config.js

Conclusión

  • Problema: next-themes accede a APIs del navegador durante SSR → hydration mismatch.
  • Solución: importar ThemeProvider dinámicamente con ssr:false y envolver tu aplicación.
  • Uso: con el wrapper en su lugar, useTheme() funciona en cualquier Client Component (ej. ThemeSwitcher).

¡Listo! Ahora tu Next.js App Router soporta dark/light mode sin errores de hidratación. ¿Preguntas o sugerencias? ¡Déjalas en los comentarios!

test...

¿Te gustó lo que leíste?

Si lo deseas, puedes apoyarme con una donación voluntaria. Tu aporte me permite dedicar más tiempo a investigar, escribir y mejorar la calidad del contenido que publico. ¡Muchísimas gracias por considerar impulsar este proyecto!

Cómprame un café

Compartir en:

Cómo evitar errores de hidratación con next-themes en Next.js (App Router) - Isco • Desarrollador de software