[Next.js 블로그 만들기] - (2)

블로그에 다크테마 적용하기

Next.js로 만든 블로그에 다크테마를 추가합니다

YEAHx4

YEAHx4

2024-10-19

요구 사항

다크 테마를 활성화하면 모든 컴포넌트가 다크 테마로 변경되어야 합니다. 혼자 하얗게 남아있는 컴포넌트 없이 전부 바뀌어야 합니다. 그리고 새로고침이나 다시 접속해도 이전에 설정한 테마가 그대로 유지되어야 합니다. 새로고침을 해도 밝은 테마였다가 어두운 테마로 바뀌면서 눈뽕을 당하는 일이 없어야 합니다. 사실 이 부분이 가장 까다로웠는데, client component나 localStorage는 처음부터 작동하지 않기 때문에 처음에 기본 테마가 표시되다가 변경되기 때문입니다. 그래서 next-themes라는 라이브러리를 사용해서 해결했습니다. 이 글을 쓰는 지금은 npm 페이지에 리드미가 보이지 않아서 next-themes Github으로 이동해서 볼 수 있습니다.

next-themes

class로 테마 지정하기

먼저 next-themes를 설치합니다.

$ npm install next-themes
# or
$ yarn add next-themes

그 다음 다크 테마를 어떤 방식으로 적용할지 선택해야 하는데, 이 블로그에서는 Tailwind CSS를 사용해서 스타일링을 진행하고 있기 때문에 테일윈드와 호환이 되야 합니다. next-themes는 class를 사용해 테마를 변경하는 기능을 지원합니다. root html 태그에 .dark, .light 클레스네임을 테마에 맞게 추가하고 이를 기반으로 테일윈드의 테마를 변경할 수 있습니다. 우선, 테일윈드에서 테마를 class를 통해 확인하도록 설정합니다.

// tailwind.config.js

const config: Config = {
  darkMode: "class",
  content: [
    "./pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./components/**/*.{js,ts,jsx,tsx,mdx}",
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {
      colors: {
        background: "var(--background)",
        foreground: "var(--foreground)",
      },
    },
  },
  plugins: [],
};
export default config;

darkMode: "class"를 지정하면 html 태그의 클레스네임에 맞춰 스타일을 적용하게 됩니다.

provider 만들기

layout.tsx에 Provider로 감싸 전역적으로 현재 테마를 받을 수 있도록 만듭니다.

import { ThemeProvider } from "next-themes";
import { ReactNode } from "react";

export default function DarkTheme({ children }: { children: ReactNode }) {
  return <ThemeProvider attribute="class">{children}</ThemeProvider>;
}

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="ko" suppressHydrationWarning>
      <head>
        {/* 생략 */}
      </head>
      <body>
        <DarkTheme>
          {/* 생략 */}
        </DarkTheme>
      </body>
    </html>
  );

이제 provider 안의 client component에서 useTheme() 훅을 통해 테마를 변경할 수 있습니다. next-themes를 사용하면 html 태그에 클래스를 넣게 되는데, 이러면 경고가 발생하기 때문에 무시해도 되긴 하지만 suppressHydrationWarning 옵션을 통해 경고를 비활성화했습니다.

ThemeChanger 만들기

지금 이 페이지에서도 보이듯 헤더 오른쪽의 아이콘을 클릭해서 원하는 테마로 변경할 수 있게 만들어 보겠습니다.

"use client";

import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
import { BsFillMoonFill, BsFillSunFill } from "react-icons/bs";

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

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

  if (!mounted) {
    return null;
  }

  const currentTheme = theme === "system" ? systemTheme : theme;

  return (
    <div
      className="cursor-pointer p-4"
      onClick={() => setTheme(currentTheme === "dark" ? "light" : "dark")}
    >
      {currentTheme === "dark" ? <BsFillMoonFill /> : <BsFillSunFill />}
    </div>
  );
}

아이콘은 react-icons를 통해 불러왔습니다. 마운트 되기 전에 currentTheme 같은 데이터를 불러올 수 없기 때문에 useEffect를 통해 마운트가 되었는지 확인했습니다. 이제 아이콘을 누르면 테마가 변경됩니다. 포스트의 내용에는 테일윈드를 적용할 수 없기 때문에 css를 직접 작성하고 있는데 html의 클래스에 .dark가 있다는 점을 이용해서 아래와 같이 작성하면 포스트의 내용도 다크 테마를 지원할 수 있습니다.

.dark .some-component {
  /* dark theme styles */
}

그리고 transition 속성을 주면 천천히 바뀌게 되어 눈을 조금 더 편안하게 할 수 있습니다. 이것으로 테마 적용 파트는 끝입니다. 다음 글에서 마크다운 렌더링으로 찾아뵙겠습니다. 감사합니다.