better-themes
Frameworks

Next.js App Router

Setup guide for Next.js App Router

Installation

npm install better-themes

Setup

Create or update your root layout

In app/layout.tsx, wrap your app with ThemeProvider:

app/layout.tsx
import { ThemeProvider } from "better-themes/rsc";

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

Important: Use the /rsc export for Next.js App Router to ensure proper server component compatibility.

Add suppressHydrationWarning

Add suppressHydrationWarning to your <html> tag to prevent hydration warnings.

Create a theme switcher component

Create a client component for theme switching:

components/theme-switcher.tsx
"use client";

import { useTheme } from "better-themes/rsc";
import { useEffect, useState } from "react";

export function ThemeSwitcher() {
  const [mounted, setMounted] = useState(false);
  const { theme, setTheme } = useTheme();

  useEffect(() => {
    setMounted(true);
  }, []);

  if (!mounted) {
    return null;
  }

  return (
    <button onClick={() => setTheme(theme === "dark" ? "light" : "dark")}>
      Toggle theme
    </button>
  );
}

Tailwind CSS Configuration

If you're using Tailwind CSS, configure it for class-based dark mode:

tailwind.config.js
export default {
  darkMode: ["class"],
  // ... rest of config
};

Complete Example

app/layout.tsx
import { ThemeProvider } from "better-themes/rsc";
import { ThemeSwitcher } from "@/components/theme-switcher";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <ThemeProvider attribute="class" disableTransitionOnChange>
          <ThemeSwitcher />
          {children}
        </ThemeProvider>
      </body>
    </html>
  );
}
components/theme-switcher.tsx
"use client";

import { useTheme } from "better-themes/rsc";
import { useEffect, useState } from "react";

export function ThemeSwitcher() {
  const [mounted, setMounted] = useState(false);
  const { theme, setTheme } = useTheme();

  useEffect(() => {
    setMounted(true);
  }, []);

  if (!mounted) {
    return null;
  }

  return (
    <button onClick={() => setTheme(theme === "dark" ? "light" : "dark")}>
      {theme === "dark" ? "Light" : "Dark"}
    </button>
  );
}

Notes

  • Always use better-themes/rsc for Next.js App Router
  • Remember to add suppressHydrationWarning to the <html> tag
  • See the SSR Guide for more details on handling server-side rendering

Example

View a complete example implementation: Next.js App Router Example

Try the live demo: Live Demo

On this page