다크 모드
규칙
- Next.js 앱에는
next-themes 사용 — 앱을 <ThemeProvider attribute="class" defaultTheme="system">로 감싸기
- 색상은 CSS 변수 사용 —
:root(라이트)와 .dark(다크)에 정의하고 var(--background)로 참조
- 시스템 환경설정 감지:
defaultTheme="system"은 prefers-color-scheme 미디어 쿼리를 따름
- 테마 토글 컴포넌트: light/dark/system 순환 —
next-themes의 useTheme() 훅 사용
- Tailwind 통합: tailwind 설정에서
darkMode: "class" 활성화, dark: 프리픽스로 오버라이드
- 하이드레이션 문제 해결:
useEffect 이후에만 테마 토글을 마운트하거나 suppressHydrationWarning 사용
- 지속성:
next-themes는 환경설정을 localStorage에 저장하고 <html>에 class로 적용
- 전환 효과: body에
transition-colors duration-200 추가로 부드러운 테마 전환
패턴
import { ThemeProvider } from "next-themes";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
</ThemeProvider>
</body>
</html>
);
}
"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 null;
return (
<button onClick={() => setTheme(theme === "dark" ? "light" : "dark")}>
{theme === "dark" ? "☀️" : "🌙"}
</button>
);
}
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
}
피해야 할 것
- CSS 변수 대신 색상을 하드코딩 — 테마 변경이 불가능해짐
<html>에 suppressHydrationWarning 누락 — 하이드레이션 오류 발생
- 잘못된 테마 플래시:
next-themes 스크립트가 페인트 전에 실행되도록 보장 (기본적으로 동작)
- 시스템 옵션 없이 라이트/다크만 지원 — 사용자 OS 환경설정을 존중해야 함