Next.js cookbook · Beginner
Add dark mode to Next.js without a flash
Support system/light/dark themes in a server-rendered Next.js app without hydration warnings or flashes.
Approach
Use next-themes: it injects a blocking script that sets the theme class/attribute before paint, persists the choice, and follows prefers-color-scheme. Add suppressHydrationWarning on <html>, style with CSS variables, and render theme-dependent UI only after mount.
Solution
// app/layout.tsx
import { ThemeProvider } from "next-themes";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<ThemeProvider attribute="data-theme" defaultTheme="system" enableSystem disableTransitionOnChange>
{children}
</ThemeProvider>
</body>
</html>
);
}
// components/ThemeToggle.tsx
"use client";
import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
export function ThemeToggle() {
const { theme, setTheme } = useTheme();
const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);
if (!mounted) return <span className="toggle-placeholder" aria-hidden />;
return (
<select aria-label="Theme" value={theme} onChange={(e) => setTheme(e.target.value)}>
<option value="system">System</option>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
);
}
:root { --bg: #fff; --fg: #111; color-scheme: light; }
[data-theme="dark"] { --bg: #0b1120; --fg: #e5e7eb; color-scheme: dark; }
body { background: var(--bg); color: var(--fg); }
Pitfalls
- Reading the theme during server render (unknown on the server).
- Hard-coded colors bypassing CSS variables.
- Animating every color change on theme switch.
Variations
- Tailwind
dark:variants withattribute="class".
Further reading: next-themes
Reference: https://github.com/pacocoursey/next-themes
More Next.js recipes
- Add a Content Security Policy with nonces in Next.js
- Add SEO metadata, Open Graph and sitemaps
- Avoid data-fetching waterfalls in Server Components
- Build a validated JSON API with Route Handlers
- Create encrypted session cookies in Next.js
- Handle errors and 404s in the App Router
- Handle forms with Server Actions and validation
- Keep secrets and server code out of the client bundle