다크모드는 이제 선택이 아니라 기본 기대치입니다. 사용자 경험과 접근성 양쪽에서 당연히 있어야 하는 기능이 됐죠. 문제는 Next.js의 서버 컴포넌트 환경에서 테마를 전환하는 일이 생각보다 까다롭다는 데 있습니다. 서버는 사용자가 어떤 테마를 골랐는지 알 길이 없으니까요.
이 글에서는 DaisyUI의 테마 시스템과 next-themes를 조합해 깜빡임 없는 다크모드를 구현한 과정을 공유합니다. 단순히 색을 반전시키는 수준을 넘어, 실제로 부딪힌 함정들을 어떻게 메웠는지까지 담았습니다.
1. 기술 스택 선택
라이브러리
역할
선택 이유
DaisyUI
UI 컴포넌트 + 테마
data-theme 기반 전환으로 CSS 변수만 바뀜
next-themes
테마 상태 관리
SSR 호환, localStorage 동기화, 시스템 테마 감지
Tailwind CSS
스타일링
DaisyUI와 자연스러운 통합
역할 분담이 깔끔합니다. DaisyUI는 "보이는 색"을, next-themes는 "어떤 테마인지 기억하고 적용하는 일"을 맡습니다. 이 둘을 분리해서 생각하면 이후 구현이 한결 단순해집니다.
2. DaisyUI 테마 설정
DaisyUI는 data-theme 속성 하나로 테마를 전환합니다. 먼저 CSS에서 사용할 테마를 선언합니다.
css
/* globals.css */@plugin "daisyui" { themes: nord --default, dim --prefersdark;}
nord → 라이트 테마 (기본값)
dim → 다크 테마 (시스템 다크모드일 때 자동 적용)
이렇게 선언해두면, 이후 우리가 할 일은 <html>의 data-theme 값을 nord와 dim 사이에서 바꿔주는 것뿐입니다. 색상 자체는 DaisyUI가 CSS 변수로 알아서 갈아끼웁니다.
3. ThemeProvider 설정
next-themes의 ThemeProvider는 클라이언트 컴포넌트로 감싸야 합니다. 여기서 가장 중요한 한 줄은 attribute="data-theme"입니다. DaisyUI가 바로 이 속성을 보고 테마를 판단하기 때문에, 이 둘을 맞춰주는 것이 연결의 핵심입니다.
tsx
// components/ThemeProvider/index.tsx'use client';import { ThemeProvider as Provider } from 'next-themes';const ThemeProvider = ({ children }: { children: React.ReactNode }) => { return ( <Provider attribute="data-theme" defaultTheme="nord"> {children} </Provider> );};