Next.js에서 커스텀 다크 모드 훅 구현하기: 타입스크립트로 간단하게
2024-10-26 08:13:081. 서론: 다크 모드의 필요성과 발전
웹 애플리케이션 사용자 경험이 점점 중요해짐에 따라 다크 모드는 필수 기능으로 자리 잡았습니다. 많은 사용자들이 밝은 화면에 지쳐가고 있는 이 시점에서, 다크 모드 지원은 단순한 유행이 아니라 필수적 요소로, 사용자들의 건강과 편안한 시각적 경험을 제공합니다. 이번 블로그 포스트에서는 Next.js와 타입스크립트를 사용하여 커스텀 다크 모드 훅을 구현하는 방법을 단계별로 작성하겠습니다.
2. 준비 사항
다크 모드 훅을 구현하기 위해서는 다음과 같은 준비 사항이 필요합니다.
- React와 Next.js에 대한 기본 지식
- Node.js가 설치된 개발 환경
- 타입스크립트가 설정된 Next.js 프로젝트
3. 단계별 다크 모드 훅 구현하기
3.1. 다크 모드 훅 생성하기
우선, 커스텀 다크 모드 훅을 생성합니다. 프로젝트의 hooks 폴더에 useDarkMode.ts 파일을 생성해주세요.
// hooks/useDarkMode.ts
import { useState, useEffect, useCallback } from 'react';
type Mode = 'light' | 'dark' | 'system';
export const useDarkMode = () => {
const [mode, setMode] = useState<Mode>('system');
const applyTheme = useCallback((isDark: boolean) => {
document.documentElement.classList.toggle('dark', isDark);
}, []);
const changeMode = useCallback((newMode: Mode) => {
setMode(newMode);
localStorage.setItem('theme', newMode);
}, []);
useEffect(() => {
const savedTheme = localStorage.getItem('theme') as Mode | null;
if (savedTheme) {
setMode(savedTheme);
}
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handleSystemThemeChange = (event: MediaQueryListEvent) => {
if (mode === 'system') {
applyTheme(event.matches);
}
};
const applyCurrentTheme = () => {
if (mode === 'system') {
applyTheme(mediaQuery.matches);
} else {
applyTheme(mode === 'dark');
}
};
applyCurrentTheme();
mediaQuery.addEventListener('change', handleSystemThemeChange);
return () => {
mediaQuery.removeEventListener('change', handleSystemThemeChange);
};
}, [mode, applyTheme]);
return { mode, changeMode };
};
3.2. 다크 모드 프로바이더 생성하기
다음으로, 다크 모드 기능을 앱 전체에서 사용할 수 있게 해주는 프로바이더 컴포넌트를 만들겠습니다. 프로젝트의 components 폴더에 DarkModeProvider.tsx 파일을 생성합니다.
// components/DarkModeProvider.tsx
import React, { createContext, useContext } from 'react';
import { useDarkMode } from '../hooks/useDarkMode';
type DarkModeContextType = ReturnType<typeof useDarkMode>;
const DarkModeContext = createContext<DarkModeContextType | undefined>(undefined);
export const DarkModeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const darkMode = useDarkMode();
return (
<DarkModeContext.Provider value={darkMode}>
{children}
</DarkModeContext.Provider>
);
};
export const useDarkModeContext = () => {
const context = useContext(DarkModeContext);
if (context === undefined) {
throw new Error('useDarkModeContext must be used within a DarkModeProvider');
}
return context;
};
3.3. 앱에 다크 모드 프로바이더 적용하기
pages/_app.tsx 파일을 업데이트하여 다크 모드 프로바이더를 추가합니다.
// pages/_app.tsx
import type { AppProps } from 'next/app';
import { DarkModeProvider } from '../components/DarkModeProvider';
import '../styles/globals.css';
function MyApp({ Component, pageProps }: AppProps) {
return (
<DarkModeProvider>
<Component {...pageProps} />
</DarkModeProvider>
);
}
export default MyApp;
3.4. 테마 토글 버튼 생성하기
사용자가 테마를 변경할 수 있도록 돕는 버튼 컴포넌트를 생성합니다. components 폴더에 ThemeToggle.tsx 파일을 생성하세요.
// components/ThemeToggle.tsx
import React from 'react';
import { useDarkModeContext } from './DarkModeProvider';
const ThemeToggle: React.FC = () => {
const { mode, changeMode } = useDarkModeContext();
return (
<select
value={mode}
onChange={(e) => changeMode(e.target.value as 'light' | 'dark' | 'system')}
className="p-2 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-800 dark:text-white"
>
<option value="light">Light</option>
<option value="dark">Dark</option>
<option value="system">System</option>
</select>
);
};
export default ThemeToggle;
3.5. 라이트 및 다크 테마에 대한 CSS 추가하기
다음으로, styles/globals.css 파일을 업데이트하여 라이트와 다크 테마 스타일을 추가합니다.
/* styles/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}
.dark {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
}
3.6. 컴포넌트에서 테마 사용하기
이제 각 컴포넌트에서 테마를 사용할 수 있습니다. pages/index.tsx 파일을 업데이트하여 다음과 같이 작성하세요.
// pages/index.tsx
import Head from 'next/head';
import ThemeToggle from '../components/ThemeToggle';
export default function Home() {
return (
<div className="min-h-screen p-4">
<Head>
<title>다크 모드 데모</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<main className="max-w-4xl mx-auto">
<h1 className="text-4xl font-bold mb-4">다크 모드 데모에 오신 것을 환영합니다!</h1>
<ThemeToggle />
<p className="mt-4">이곳은 테마 변경을 보여주는 샘플 텍스트입니다.</p>
</main>
</div>
);
}
3.7. Tailwind CSS에서 다크 모드 설정하기
다음은 tailwind.config.js 파일을 업데이트하여 다크 모드에 대한 'class' 전략을 활성화합니다.
// tailwind.config.js
module.exports = {
darkMode: 'class',
// ... 나머지 설정
};
3.8. 다크 모드 테스트하기
Next.js 애플리케이션을 실행해 보세요.
npm run dev
브라우저에서 http://localhost:3000에 접속하면 시스템 기본 설정을 따르며 수동으로 다크 모드를 설정할 수 있는 동작하는 다크 모드 토글이 적용된 애플리케이션을 확인할 수 있습니다.
4. 결론
축하합니다! Next.js 애플리케이션에서 커스텀 다크 모드 훅을 성공적으로 구현했습니다. 이 구현은 React 훅을 사용하여 상태 관리와 시스템 기본 설정을 존중하며 수동으로 변경할 수 있도록 합니다. 추가로 Tailwind CSS를 이용하여 스타일링을 했습니다.
다크 모드를 구현할 때는 접근성을 고려해야 하며, 두 가지 테마에 대한 텍스트와 배경 색상 간의 충분한 대비를 유지하는 것이 중요합니다.