next-themes 를 이용하여 Dark Mode 적용하기
오늘은 TODO List 에 작성한 목록 중 하나인 시스템 모드에 따라 다크모드/라이트모드 전환 을 적용해보려고 합니다.

오늘은 TODO List 에 작성한 목록 중 하나인 시스템 모드에 따라 다크모드/라이트모드 전환 을 적용해보려고 합니다.
현재 제 블로그는 다크/라이트 모드 토글 기능 및 Tailwind CSS class 방식 구현이 된 상태이나, localStorage 저장 (새로고침 시 초기상태로 복원) 및 시스템 설정 감지가 없는 상태입니다. (OS 설정 무시)
이에 따라 next-themes 를 이용하여 사용자의 OS에 설정한 다크모드를 자동으로 감지하여 보여주는 방식으로 진행하려고 합니다.
next-themes는 Next.js 전용으로 최적화된 테마 관리 라이브러리로, 시스템 설정 감지, 다크/라이트 모드 전환, 페이지 로딩 시 깜빡임 방지 등을 자동으로 처리합니다.
🤔 잠깐, next-themes 라이브러리, 정말 필요할까?
next-themes를 사용하면 좋은 경우:
- 시스템 설정 감지가 필요한 경우
- 사용자가 OS에 설정한 다크모드를 자동으로 감지
- 처음 방문 시 사용자 시스템 설정 존중
- UX 개선이 필요한 경우
- 새로고침해도 사용자가 선택한 테마 유지
- 탭 간 테마 동기화 (여러 탭 열었을 때 모두 같은 테마)
- 플래시 없음 (페이지 로드 시 깜빡임 방지)
- 엔터프라이즈 애플리케이션
- 복잡한 테마 전환 로직
- 다중 테마 지원
- SSR/SSG 호환성
직접 구현해도 되는 경우:
- 간단한 다크모드만 필요
- 시스템 설정은 무시해도 괜찮음
- 새로고침 후 초기값으로 돌아가도 괜찮음
next-themes 적용하기
1. 설치하기
npm install next-themes혹은
yarn add next-themes2. 레이아웃에 ThemeProvider 설정
// app/layout.tsx
import type { Metadata } from "next";
import { ThemeProvider } from "next-themes";
import "./globals.css";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html suppressHydrationWarning>
<body>
<ThemeProvider attribute="class" enableSystem>
{children}
</ThemeProvider>
</body>
</html>
);
}저는 tailwind 를 통해 class로 이미 구성하고 있었기에 attribute="class" 를 작성하였습니다.
기본 값은 <html data-theme="dark"> 로 구성되게 합니다.
[data-theme='dark'] {
--background: black;
--foreground: white;
}이런식으로 CSS-in-JS 라이브러리 사용하거나 여러 테마를 지원할 때에는 기본값으로 작성해주시면 됩니다.
3. Header.tsx 수정하기 (다크/라이트모드 토글기능)
// 기존 Header 코드
const Header = () => {
const [isDark, setIsDark] = useState(false);
const [isVisible, setIsVisible] = useState(true);
const [lastScrollY, setLastScrollY] = useState(0);
useEffect(() => {
if (isDark) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
}, [isDark]);
...
return (
...
<button
className="cursor-pointer rounded-full px-4 py-[10px] hover:bg-slate-100 dark:hover:bg-[#11161b]"
aria-label={isDark ? "라이트 모드로 변경" : "다크 모드로 변경"}
title={isDark ? "라이트 모드" : "다크 모드"}
onClick={() => setIsDark((prev) => !prev)}
>
{isDark ? (
<LightModeIcon className="size-[24px]" />
) : (
<DarkModeIcon className="size-[24px]" />
)}
</button>
...
);기존에는 useState 와 useEffect 를 사용하여 직접 dark 클래스를 추가/제거 해주었습니다.
next-theme 에서 useTheme 훅을 지원해주기 때문에 useState , useEffect 를 제거해주고 다음과 같이 작성해줍니다.
const { resolvedTheme, setTheme } = useTheme();
{resolvedTheme === 'dark' ? (
<LightModeIcon className="size-[24px]" />
) : (
<DarkModeIcon className="size-[24px]" />
)}Hydration 방지
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
// 서버와 클라이언트 렌더링이 일치할 때까지 기다리기
if (!mounted) {
return null;
} 이제는 사용자의 시스템 설정에 맞추어 홈페이지가 자동으로 다크모드 / 라이트모드 토글이 가능해졌습니다.
저는 원래 다크모드 / 라이트모드 같은 테마 전환에 대한 니즈가 있었어서 프로젝트 초반부터 dark 모드를 염두 해두었었기에 쉽게 적용 할 수 있었던것 같아요.
다크모드를 진행하면서 어떻게 해야 사용자에게 위화감 없이 보여주게 될까 라는 생각도 듭니다.
아직 UX에 대한 지식이 부족한터라 다른 사이트들을 많이 참고하며 공부를 해보아야겠어요.