Next.js + TailwindCSS v4: Next-Themes로 다크/라이트 테마 추가하는 방법

발행: (2025년 12월 20일 오후 10:53 GMT+9)
7 min read
원문: Dev.to

Source: Dev.to

TailwindCSS v4는 이제 여러 개의 config 파일을 유지하지 않으며—모든 것이 단일 global.css 파일 안에 들어갑니다. 이로 인해 테마 적용이 다소 까다롭게 느껴질 수 있습니다. 이 가이드에서는 Next.js 프로젝트에서 next‑themes를 사용해 밝은 테마, 어두운 테마, 그리고 사용자 정의 테마까지 손쉽게 설정하는 방법을 보여드리겠습니다.

Step 01 – 프로젝트 시작

👉 Next.js 프로젝트 설치

pnpm create next-app my-project-name
pnpm install
pnpm dev

👉 Next‑Themes 설치

pnpm add next-themes

Source:

Step 02 – layout.tsx 수정

👉 애플리케이션(자식 컴포넌트)을 <ThemeProvider> 로 감싸기

아래는 기본 Next.js 코드를 유지하면서 프로바이더를 추가한 최소 layout.tsx 예시입니다.

import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import { ThemeProvider } from "next-themes";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly) {
  return (
    
      
        
          {children}
        
      
    
  );
}

👉 ThemeProvider에 속성 추가하기


  {children}

속성 설명

속성설명
enableSystem={true}앱이 사용자의 OS 수준 색상 스키마 설정을 따르도록 합니다. (false가 기본값)
defaultTheme="system"첫 방문 시 어떤 테마가 로드될지 결정합니다. "system"을 사용하면 OS 설정을 그대로 반영합니다.

⚠️ Hydration 경고 해결

“A tree hydrated but some attributes of the server‑rendered HTML didn’t match the client properties” 라는 경고가 표시되면 <ThemeProvider> 태그에 suppressHydrationWarning 을 추가합니다:


  
    
      {children}
    
  

Note: ThemeProvider클라이언트 컴포넌트이며, 서버 컴포넌트가 아닙니다.

Step 03 – 색상 사용자 정의

👉 global.css

Tailwind import와 다크 모드용 커스텀 변형을 추가합니다:

@import 'tailwindcss';

/* Enable dark variant based on data-theme attribute */
@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));

Tailwind v4에서는 @import 'tailwindcss'가 필요합니다.
@custom-variant 라인은 Tailwind의 dark: 유틸리티가 [data-theme="dark"] 속성과 함께 동작하도록 합니다.

사용 예시

  
  
    Next Theme With TailwindCSS v4
  

다크 테마가 활성화되면 bg-amber-500가 적용됩니다.

👉 테마 변수 정의

@theme {
  /* Light mode background */
  --bg-color-light-default: hsl(220, 14%, 96%);
  /* Dark mode background */
  --bg-color-dark-default: hsl(207, 95%, 8%);
}

👉 변수들을 루트에 적용

:root[data-theme="light"] {
  background-color: var(--bg-color-light-default);
}

:root[data-theme="dark"] {
  background-color: var(--bg-color-dark-default);
}

이제 전체 애플리케이션이 활성화된 테마에 따라 적절한 배경 색상을 상속받게 됩니다.

Step 04 – 테마 토글러 버튼 추가

우리는 토글 버튼에 lucide‑react 아이콘 라이브러리를 사용할 것입니다.

아이콘 설치

pnpm install lucide-react

ThemeTogglerBtn.tsx 만들기

'use client';

import { useTheme } } from "next-themes";
import { useEffect, useState } from "react";
import { Sun, Moon } from "lucide-react";

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

  // 컴포넌트가 하이드레이션된 후에만 렌더링되도록 보장
  useEffect(() => setMounted(true), []);

  const toggleTheme = () => {
    setTheme(resolvedTheme === "dark" ? "light" : "dark");
  };

  if (!mounted) {
    return (
      
    );
  }

  const currentIcon =
    resolvedTheme === "dark" ? (
      
    ) : (
      
    );

  return (
    
      {currentIcon}
      
        {resolvedTheme === "dark" ? "Switch to light theme" : "Switch to dark theme"}
      
    
  );
}

이제 ThemeTogglerBtn을 UI 어디에서든(예: 헤더) 임포트하여 사용자가 라이트 모드와 다크 모드 사이를 전환할 수 있게 할 수 있습니다.

🎉 끝!

이제 다음을 갖추었습니다:

  1. next‑themes가 설정된 Next.js 프로젝트.
  2. data-theme 속성을 인식하도록 구성된 Tailwind v4.
  3. 라이트/다크 배경을 위한 커스텀 CSS 변수.
  4. 재사용 가능한 클라이언트‑사이드 토글 버튼.

추가적인 커스텀 팔레트나 “시스템‑전용” 모드 등으로 테마 시스템을 확장해 보세요. 즐거운 코딩 되세요!

테마 전환 버튼


  {currentIcon}
  Theme switcher button

useTheme

import { useTheme } from 'next-themes';

const { theme, setTheme, resolvedTheme } = useTheme();
변수설명
theme현재 선택된 테마('light' 또는 'dark')를 표시합니다.
setTheme테마를 변경할 수 있는 설정 함수입니다.
resolvedTheme시스템 기본 테마를 감지합니다(사용자 기기에서 실제로 활성화된 테마).

mounted State

When using next-themes in a Next.js app, the theme value can differ between server‑side rendering (SSR) and client‑side hydration. To avoid a mismatch, we track a mounted flag that becomes true only after the component has mounted on the client.

import { useState, useEffect } from 'react';

const [mounted, setMounted] = useState(false);

useEffect(() => {
  setMounted(true);
}, []);
  • 초기: mounted = false (SSR).
  • 클라이언트 마운트 후: mounted = true.

This ensures the theme toggler runs only on the client side.

테마 토글러 함수

const toggleTheme = () => {
  setTheme(resolvedTheme === 'dark' ? 'light' : 'dark');
};
  • resolvedTheme'dark'이면, 함수는 'light'로 전환합니다.
  • resolvedTheme'light'이면, 'dark'로 전환합니다.

Icon Selection

import { Sun, Moon } from 'react-feather';

const currentIcon = resolvedTheme === 'dark' ? (
  
) : (
  
);
  • Dark mode: Sun 아이콘을 렌더링합니다.
  • Light mode: Moon 아이콘을 렌더링합니다.

버튼 UI


  {currentIcon}
  Theme switcher button
  • 버튼을 클릭하면 toggleTheme가 실행됩니다.
  • {currentIcon}은(는) 적절한 아이콘을 표시합니다.
  • 은(는) 화면 판독기를 위한 접근성 라벨을 제공합니다.

✅ 한눈에 보기

  1. 설치 next-themes.
  2. 감싸기 앱을 <ThemeProvider> 로.
  3. 설정 global.css@custom-variant dark (또는 Tailwind의 dark: 변형)와 함께.
  4. 추가 useTheme 훅을 사용하여 테마 토글러 버튼을.
  5. 즐기세요 부드러운 다크/라이트 테마 전환을! 🎉
Back to Blog

관련 글

더 보기 »