Next.js + Tailwind CSSで、 ちらつかないダークモードを実装する

Next.js 14のApp Routerでダークモードを実装したときのメモ。 Tailwind CSSを利用。アプリはSSRでレンダリングする。

下記のサイトをほぼそのまま実装した。

App Directory dark mode with tailwindcss and SSR · vercel/next.js · Discussion #47952's image

App Directory dark mode with tailwindcss and SSR · vercel/next.js · Discussion #47952

Dark mode in app dir with tailwindcss and SSR Get theme from cookies in main layout // app/layout.tsx export default function RootLayout({ children, }: { children: React.ReactNode }) { const theme ...

LocalStorageを利用すると、ページをリロードしたときにちらついてしまう。そのため、Cookieを利用する。

tailwind.config.jsdarkMode: 'class'を追加する。

tailwind.config.js
const config: Config = {
  // ...
+ darkMode: 'class',
};

app/layout.tsxでcookieからthemeの値を取得し、htmlタグのclassNameに設定する。

app/layout.tsx
import { cookies } from 'next/headers';

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  const theme = cookies().get('theme');
  return (
    <html lang="ja" className={theme?.value}>
      {/* ... */}
    </html>
  );
}

typeとしてテーマを定義する。

app/_types/theme.ts
const Theme = {
  Light: 'light',
  Dark: 'dark',
} as const;

type Theme = (typeof Theme)[keyof typeof Theme];

export default Theme;

テーマを切り替えるためのボタンを実装する。

app/_components/ThemeSwitch.tsx
'use client';
import { useState } from 'react';

import Theme from '@/app/_types/theme';

interface ThemeSwitchProps {
  theme: Theme;
  className?: string;
}

export default function ThemeSwitch(props: Readonly<ThemeSwitchProps>) {
  const [theme, setTheme] = useState<Theme>(props.theme);

  const toggleTheme = () => {
    const root = document.getElementsByTagName('html')[0];
    root.classList.toggle(Theme.Dark);
    if (root.classList.contains(Theme.Dark)) {
      setTheme(Theme.Dark);
      document.cookie = `theme=${Theme.Dark}`;
    } else {
      setTheme(Theme.Light);
      document.cookie = `theme=${Theme.Light}`;
    }
  };

  return (
    <button
      className={['bg-gray-300 dark:bg-gray-700', props.className].join(' ')}
      onClick={toggleTheme}
    >
      {theme == Theme.Light ? Theme.Light : Theme.Dark}
    </button>
  );
}

ThemeSwitchを表示する。今回はapp/_components/MainHeader.tsxで表示する例を示すが、任意の場所で構わない。

app/_components/MainHeader.tsx
import { cookies } from 'next/headers';

import ThemeSwitch from './ThemeSwitch';
import Theme from '@/app/_types/theme';

export default function MainHeader() {
  const theme =
    cookies().get('theme')?.value === Theme.Dark ? Theme.Dark : Theme.Light;
  return (
    <header>
      <ThemeSwitch theme={theme} />
    </header>
  );
}

テーマ切り替えボタンをクリックして、テーマが切り替わることを確認する。

App Directory dark mode with tailwindcss and SSR · vercel/next.js · Discussion #47952's image

App Directory dark mode with tailwindcss and SSR · vercel/next.js · Discussion #47952

Dark mode in app dir with tailwindcss and SSR Get theme from cookies in main layout // app/layout.tsx export default function RootLayout({ children, }: { children: React.ReactNode }) { const theme ...

Licensed under CC BY-NC-SA 4.0
最終更新 Jan 20, 2024
Built with Hugo
テーマ StackJimmy によって設計されています。