DEV Community

Cover image for How to support dark mode in Next.js App Router with almost no "use client"🌞🌜
Esuki
Esuki

Posted on • Edited on

How to support dark mode in Next.js App Router with almost no "use client"🌞🌜

Demo

https://next13-dark.vercel.app
The version of Next.js is 13.4.10.

Conclusion

  • Saving and reading the current theme using the cookies
  • The theme change using the Server Actions
  • Thematic styles can be set with CSS Modules or Tailwind CSS

Step 1: Enable Server Actions

// next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    appDir: true,
    serverActions: true // Add,
  },
};

module.exports = nextConfig;
Enter fullscreen mode Exit fullscreen mode

Step 2: Set the current theme to html tag

// layout.tsx

import { cookies } from "next/headers";

import ThemeToggle from "./_components/themeToggle"

export default function RootLayout({ children }: { children: React.ReactNode }) {
  const defaultTheme = "light";
  const cookieValue = cookies().get("theme")?.value || "";
  const isTheme = cookieValue === defaultTheme || cookieValue === "dark";
  const theme = isTheme ? cookieValue : defaultTheme;

  return (
    <html className={theme}>
      <body>
        <ThemeToggle currentTheme={theme} />

        {children}
      </body>
    </html>
  );
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Create a button to switch themes

// themeToggle.tsx

"use client"; // Required to set the onClick.

import { setTheme } from "./action";

export default function ThemeToggle({ currentTheme }: { currentTheme: string }) {
  const changeTheme = () => setTheme(currentTheme);

  return (
    <button onClick={changeTheme}>
      {currentTheme === "light" ? "Dark" : "Light"}
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Create a Server Actions to change the theme

"use server";

import { cookies } from "next/headers";
import { revalidatePath } from "next/cache";

export const setTheme = (currentTheme = "") => {
  const newTheme = currentTheme === "light" ? "dark" : "light";
  cookies().set("theme", newTheme); // This method can only be used with `Server Actions` or `Route Handlers`.
  revalidatePath("/");
};
Enter fullscreen mode Exit fullscreen mode

Step 5: Change styles by theme

// globals.scss

html {
  background: white;
  color: black;
  &.dark {
    background: black;
    color: white;
  }
}

a {
  color: steelblue;
  html.dark & {
    color: skyblue;
  }
}
Enter fullscreen mode Exit fullscreen mode

For Scss Modules.

// page.module.scss

.wrap {
  background: white;
  color: black;
}

:global(html.dark) {
  .wrap {
    background: black;
    color: white;
  }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (2)

Collapse
 
svey profile image
Hayden Soule

This is awesome! Exactly what I was looking for; there shouldn't be a need to client side render so many components just to support a stateful darkmode.

Collapse
 
antoniocolagreco profile image
Antonio Colagreco

Thank you