개발일지/Next.js

[LocalStorage/TailwindCSS] Next.js에서 로컬스토리지에 저장하기 - #1 (멀티 테마 적용)

ayeongjin 2025. 2. 23. 16:12

🖍️ Next.js에서 로컬스토리지에 저장하기 - #1 (멀티 테마 적용)

다이어리 프로젝트이기 때문에 사용자 맞춤형 UI를 제공하여 개인적인 꾸미기와 사용자 환경을 최적화하는 기능을 추가했다. 이를 위해 전역 스타일 컬러 변경과 폰트 설정 기능을 구현했다

 


 

1. Tailwind CSS를 활용한 전역 테마 컬러 설정

사용자는 원하는 테마 색상을 선택하여 자신의 다이어리를 커스터마이징할 수 있다. Tailwind CSS를 사용하여 CSS 변수(var(--color-500)) 기반으로 테마 컬러를 관리하도록 설정했다.

 

Tailwind 설정 (tailwind.config.ts)

Tailwind CSS는 tailwind.config.ts에서 사용자 정의 색상을 추가할 수 있도록 지원한다. 여기서 CSS 변수를 기반으로 색상을 정의하면, 이후 테마 변경 시 동적으로 색상이 적용될 수 있다.

  theme: {
    extend: {
      colors: {
        theme: {
          50: "var(--color-50)",
          100: "var(--color-100)",
          200: "var(--color-200)",
          300: "var(--color-300)",
          400: "var(--color-400)",
          500: "var(--color-500)",
          600: "var(--color-600)",
          700: "var(--color-700)",
          800: "var(--color-800)",
          900: "var(--color-900)",
        },
      },
    },
  },

CSS 변수(--color-500 등)를 기반으로 테마 색상을 설정하고, 이를 Tailwind에서 활용할 수 있도록 설정한다. 이후 글로벌 스타일에서 테마별로 색상을 정의할 수 있다.

 

글로벌 스타일에 테마 컬러 적용 (global.css)

Tailwind 설정에서 정의한 theme 색상을 적용하기 위해, global.css에서 각 테마별 색상 변수를 정의한다. 브라우저가 특정 data-theme 값을 감지하면 해당하는 색상 변수를 적용하도록 설정한다.

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  /* Sky 테마 */
  [data-theme="sky"] {
    --color-50: #f0f9ff;
    --color-100: #e0f2fe;
    --color-200: #bae6fd;
    --color-300: #7dd3fc;
    --color-400: #38bdf8;
    --color-500: #0ea5e9;
    --color-600: #0284c7;
    --color-700: #0369a1;
    --color-800: #075985;
    --color-900: #0c4a6e;
  }

  /* Pink 테마 */
  [data-theme="pink"] {
    --color-50: #fdf2f8;
    --color-100: #fce7f3;
    --color-200: #fbcfe8;
    --color-300: #f9a8d4;
    --color-400: #f472b6;
    --color-500: #ec4899;
    --color-600: #db2777;
    --color-700: #be185d;
    --color-800: #9d174d;
    --color-900: #831843;
  }
}

이렇게 설정하면 Tailwind에서 theme 클래스를 사용할 때 자동으로 CSS 변수를 참조하여 테마가 적용된다.

 


 

2.ThemeContext를 활용한 동적 테마 변경

사용자가 선택한 테마를 localStorage에 저장하고, 새로고침 후에도 유지되도록 ThemeContext를 설정했다. 이렇게 하면 브라우저가 새로고침되더라도 사용자가 선택한 테마가 유지된다.

 

ThemeContext.tsx 생성 및 구현

"use client";

import { createContext, ReactNode, useContext, useEffect, useState } from "react";

interface ThemeContextProps {
  theme: string;
  setTheme: (theme: string) => void;
}

const ThemeContext = createContext<ThemeContextProps | undefined>(undefined);

interface ThemeProviderProps {
  children: ReactNode;
  initialTheme: string;
}

export function ThemeProvider({ children, initialTheme }: ThemeProviderProps) {
  const [theme, setTheme] = useState<string | null>(null);
	
  // LocalStorage에서 적용된 테마 가져오기
  useEffect(() => {
    if (typeof window !== "undefined") {
      const savedTheme = localStorage.getItem("theme") || initialTheme;
      setTheme(savedTheme);
    }
  }, []);
	
  // 테마 변경
  useEffect(() => {
    if (theme) {
      document.documentElement.setAttribute("data-theme", theme);
      localStorage.setItem("theme", theme);
    }
  }, [theme]);

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

export function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error("useTheme 훅은 ThemeProvider 내부에서만 사용해야 합니다.");
  }
  return context;
}

이제 사용자가 선택한 테마를 적용하면 data-theme 속성으로 반영되고, localStorage에 저장하여 유지할 수 있다.

 


 

3. ThemeProvider를 RootLayout에 적용

이제 ThemeProvider를 RootLayout에서 적용하여 모든 페이지에서 테마를 사용할 수 있도록 설정한다.

 

✅ layout.tsx 적용

export default function RootLayout({
  children,
}: Readonly<{ children: React.ReactNode }>) {
  return (
    <html lang="ko">
      <body className="bg-theme-50 flex flex-col items-center">
        <ThemeProvider initialTheme="sky">
          <Header />
          <main className="flex w-full md:w-3/4 lg:w-1/2 mx-auto min-h-screen p-4">
            {children}
          </main>
          <Navbar />
        </ThemeProvider>
      </body>
    </html>
  );
}

이렇게 하면 모든 페이지에서 useTheme()를 사용할 수 있으며, 테마가 유지된다.

 

✅ useTheme() 사용 예시

"use client";

import { useTheme } from "@/context/ThemeContext";
import { Theme } from "@/types/settingTypes";

export default function ThemeSelectList() {
  const { theme, setTheme } = useTheme();

  const themes: Theme[] = ["sky", "amber", "lime", "pink", "stone"];

  return (
    <div className="w-full px-4 flex flex-row justify-evenly items-center gap-6">
      {themes.map((t) => (
        <ThemeSelect
          key={t}
          value={t}
          selected={t === theme}
          onChange={() => setTheme(t)}
        />
      ))}
    </div>
  );
}

 


 

마무리

  • Tailwind CSS + CSS 변수로 전역 테마 컬러를 설정하여 동적 테마 적용
  • ThemeContext를 활용해 테마를 변경하고 localStorage에 저장하여 유지
  • 전역적으로 ThemeProvider를 RootLayout에서 적용하여 모든 페이지에서 테마 관리 가능