Next.js + Tailwind CSS 모범 사례: 설정부터 다크 모드까지 완벽 가이드(2025년판)

VS Code에서 그 버튼 컴포넌트의 className을 보니 클래스가 스물세 개나 있었습니다. bg-blue-500부터 dark:hover:bg-blue-800까지 한 줄에 빽빽하게 들어차 가로 스크롤바가 끝없이 늘어났죠. 자리를 지나가던 동료가 화면을 흘끗 보더니 물었습니다. “이게 대체 무슨 코드예요?”
그 순간 뭐라고 답해야 할지 몰랐습니다. Tailwind를 쓴 지 거의 2년, 빠른 건 사실이지만 코드는 갈수록 암호문처럼 변했습니다. 복사해서 붙일 때는 편해도 수정할 때는 지옥이었습니다. 모든 버튼의 모서리를 한꺼번에 바꾸려면 파일마다 rounded-lg를 찾아 하나씩 고쳐야 했습니다.
이런 고통은 저만 겪는 게 아닙니다. 2025년 Tailwind CSS는 v4로, Next.js는 15로 올라오면서 설정 방식, 다크 모드, 성능 최적화가 모두 달라졌습니다. 처음에는 저도 설정 파일은 어디로 갔는지, darkMode: 'class'는 왜 없는지 몰라 헤맸습니다. 여러 문제를 겪고 나서야 전체 흐름을 이해하게 됐습니다.
이 글에서는 최근 프로젝트에서 직접 확인한 방법을 나눕니다. 공식 문서를 그대로 되풀이하는 대신 클래스명 폭증을 막는 방법, 다크 모드를 깔끔하게 구현하는 방법, CSS 번들을 500KB에서 50KB로 줄이는 방법을 실제 경험을 바탕으로 설명합니다. Tailwind의 긴 클래스명 때문에 힘들었거나 v4 업그레이드를 고민 중이라면 계속 읽어보세요.
2025년의 변화: Tailwind CSS v4 + Next.js 15
먼저 v4의 가장 큰 변화부터 보겠습니다. 설정 파일이 사라졌습니다.
익숙했던 tailwind.config.js가 v4에서는 선택 사항이 됐습니다. 처음 소식을 접했을 때는 농담인 줄 알았지만 Next.js 15.3 새 프로젝트를 열어보니 정말 없었습니다. Tailwind 팀은 이를 “제로 설정 철학”이라고 부릅니다. 프로젝트 파일을 자동으로 스캔해 바로 사용할 수 있습니다.
그렇다고 사용자 정의가 불가능한 것은 아닙니다. 오히려 v4는 사용자 정의 설정을 더 직관적인 global.css로 옮겼습니다. 이제 테마 색상, 간격, 글꼴을 CSS 변수로 정의합니다.
@theme {
--color-primary: #3b82f6;
--color-secondary: #8b5cf6;
--font-sans: 'Inter', sans-serif;
}
처음에는 퇴보한 게 아닌가 싶었지만 이틀쯤 써보니 오히려 더 빨리 수정할 수 있었습니다. 예전에는 테마 색상을 바꿀 때 개발 서버를 재시작하고 컴파일을 기다렸지만, 지금은 CSS 변수를 고치면 핫 리로드에 즉시 반영됩니다. 디자이너도 CSS 변수를 이해할 수 있어 “blue-500이 대체 어떤 파란색이냐”고 묻지 않아도 됩니다.
속도도 확실히 좋아졌습니다. v4는 기반을 Rust로 다시 작성했고 공식 설명에 따르면 5배 빨라졌습니다. 제가 테스트했을 때 콜드 스타트는 8초에서 2초 미만으로 줄었습니다. 단순한 벤치마크 숫자가 아닙니다. 하루에도 여러 번 개발 서버를 켜는 만큼 절약한 시간으로 커피를 두 잔은 더 마실 수 있습니다.
Next.js 15에서는 App Router가 표준입니다. Tailwind와 함께 쓰면 서버 컴포넌트의 스타일 격리가 잘 작동해 스타일 오염을 걱정할 필요가 없습니다. 단, 클라이언트 컴포넌트에는 'use client'를 붙여야 다크 모드 전환 문제가 생기지 않습니다. 뒤에서 자세히 설명하겠습니다.
또 하나 놓치기 쉬운 변화는 v4의 기본 border-color가 currentColor로 바뀌었다는 점입니다. 색상을 지정하지 않은 테두리는 글자 색상을 따릅니다. 별일 아닌 듯하지만 v3에서 바로 업그레이드하면 많은 테두리가 “사라진” 것처럼 보일 수 있습니다. 실제로는 글자와 같은 색으로 변한 것입니다. 저도 이 문제의 원인을 찾느라 한참 걸렸습니다.
정리하면 v4의 변화는 크지만 방향은 옳습니다. 더 빠르고 단순하며 직관적입니다. 처음 적응 기간만 지나면 이전 방식으로 돌아가기 어렵습니다.
지나치게 긴 클래스명 해결하기: 올바른 컴포넌트 캡슐화
처음의 문제로 돌아가 보겠습니다. 클래스가 스무 개 넘는 버튼은 어떻게 해야 할까요?
많은 사람이 먼저 @apply를 떠올립니다. Tailwind 클래스를 CSS 파일에 넣고 .btn-primary라는 이름을 붙이면 깔끔해 보입니다. 저도 그렇게 했지만 Tailwind 창시자 Adam Wathan이 Twitter에서 “@apply를 많이 쓴다면 Tailwind의 설계 철학을 오해하고 있을 수 있다”고 한 말을 봤습니다.
듣기 불편해도 일리가 있습니다. @apply는 유틸리티 클래스를 CSS 파일에 미리 컴파일하므로 Tailwind의 주문형 생성 장점이 사라집니다. 캡슐화한다고 생각했지만 실제로는 CSS 번들을 수동으로 부풀리는 셈입니다. 예전에 한 프로젝트에서 @apply를 과도하게 사용했다가 프로덕션 CSS가 30KB에서 120KB로 늘었습니다.
올바른 방법은 컴포넌트 캡슐화입니다.
자주 쓰는 스타일 조합을 React 컴포넌트로 묶습니다. 클래스 수는 같지만 한 번만 작성하면 됩니다.
// ❌ 이전: 사용하는 곳마다 반복 작성
<button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-lg shadow-md transition duration-200">
제출
</button>
// ✅ 현재: 컴포넌트로 캡슐화
<Button variant="primary">제출</Button>
Button 컴포넌트 내부의 클래스는 그대로지만 사용하는 코드는 깔끔해집니다. 버튼 스타일을 일괄 조정할 때도 파일 하나만 고치면 됩니다.
하지만 이것만으로는 부족합니다. 버튼에는 기본, 보조, 위험 작업 등 여러 상태가 있습니다. 상태마다 컴포넌트를 만들 수는 없습니다. 이때 cva(class-variance-authority)가 필요합니다.
이 라이브러리는 컴포넌트 variant를 관리하기 위한 것으로 Tailwind와 잘 어울립니다.
import { cva, type VariantProps } from 'class-variance-authority'
const buttonStyles = cva(
// 기본 스타일
'font-bold rounded-lg transition duration-200',
{
variants: {
variant: {
primary: 'bg-blue-500 hover:bg-blue-700 text-white',
secondary: 'bg-gray-200 hover:bg-gray-300 text-gray-800',
danger: 'bg-red-500 hover:bg-red-700 text-white'
},
size: {
sm: 'py-1 px-3 text-sm',
md: 'py-2 px-4',
lg: 'py-3 px-6 text-lg'
}
},
defaultVariants: {
variant: 'primary',
size: 'md'
}
}
)
export function Button({
variant,
size,
children,
...props
}: VariantProps<typeof buttonStyles> & React.ButtonHTMLAttributes<HTMLButtonElement>) {
return (
<button className={buttonStyles({ variant, size })} {...props}>
{children}
</button>
)
}
이제 사용하기가 훨씬 편합니다.
<Button variant="primary">저장</Button>
<Button variant="danger" size="lg">삭제</Button>
<Button variant="secondary" size="sm">취소</Button>
TypeScript가 잘못 입력한 variant 이름도 바로 검사합니다. shadcn/ui 역시 이 방식을 사용하므로 컴포넌트 라이브러리 코드가 특히 깔끔합니다.
물론 @apply를 전혀 쓰면 안 된다는 뜻은 아닙니다. 서드파티 라이브러리 스타일을 바꿀 때처럼 컴포넌트로 감쌀 수 없다면 @apply로 덮어써도 괜찮습니다. 다만 자체 컴포넌트는 가능하면 캡슐화하세요.
사용자 정의 테마 설정: 나만의 디자인 시스템 만들기
컴포넌트를 캡슐화한 다음에는 프로젝트 전체의 디자인을 어떻게 통일할지가 문제입니다.
예전에는 한 프로젝트에서 blue-400, blue-500, #3B82F6, rgb(59, 130, 246) 등 대여섯 가지 파란색을 썼습니다. 디자이너는 어느 규칙을 따른 것이냐며 난감해했습니다. 그제야 색상, 글꼴, 간격을 고정한 디자인 시스템이 필요하다는 걸 알았습니다.
v4에서는 앞서 본 @theme에서 간단히 정의할 수 있습니다.
/* app/globals.css */
@import 'tailwindcss';
@theme {
/* 브랜드 색상 */
--color-brand-primary: #3b82f6;
--color-brand-secondary: #8b5cf6;
/* 의미 기반 색상 */
--color-success: #10b981;
--color-warning: #f59e0b;
--color-error: #ef4444;
/* 중립 색상(밝은색부터 어두운색까지) */
--color-neutral-50: #f9fafb;
--color-neutral-100: #f3f4f6;
--color-neutral-500: #6b7280;
--color-neutral-900: #111827;
/* 글꼴 패밀리 */
--font-sans: 'Inter', system-ui, sans-serif;
--font-mono: 'Fira Code', monospace;
/* 간격(디자인은 8px 그리드) */
--spacing-unit: 0.5rem; /* 8px */
/* 모서리 반경 */
--radius-sm: 0.25rem;
--radius-md: 0.5rem;
--radius-lg: 1rem;
}
정의한 뒤 Tailwind 클래스에서 바로 사용합니다.
<div className="bg-brand-primary text-neutral-50 rounded-md">
테마 색상 배경
</div>
bg-blue-500 대신 bg-brand-primary를 쓴 점에 주목하세요. 브랜드 색상을 바꾸려면 변수 하나만 수정하면 사이트 전체에 반영됩니다. blue-500을 grep으로 찾아 수백 번 고칠 필요가 없습니다.
v3 설정 파일 방식을 유지하고 싶다면 tailwind.config.ts를 만들 수도 있습니다.
import type { Config } from 'tailwindcss'
export default {
content: [
'./app/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
// 기본 테마 확장(권장)
colors: {
brand: {
primary: '#3b82f6',
secondary: '#8b5cf6',
},
},
fontFamily: {
sans: ['Inter', 'sans-serif'],
},
},
},
} satisfies Config
extend가 중요합니다. theme.colors를 직접 작성하면 Tailwind의 기본 색상을 모두 덮어써 bg-red-500 등을 쓸 수 없습니다. extend는 대체가 아니라 추가입니다.
CSS 변수와 Tailwind 설정을 함께 사용하면 런타임 테마 전환도 가능합니다.
:root {
--color-primary: #3b82f6;
}
[data-theme='purple'] {
--color-primary: #8b5cf6;
}
// tailwind.config.ts
colors: {
primary: 'var(--color-primary)',
}
테마를 전환할 때 CSS를 다시 컴파일할 필요 없이 DOM 속성만 바꾸면 됩니다. 사용자가 직접 테마 색상을 고르는 SaaS 제품에서 특히 유용합니다.
디자인 시스템을 갖추면 협업도 원활해집니다. 새 팀원도 globals.css만 보면 어떤 색상을 써야 할지 알 수 있어 임의의 파란색이 추가되는 일을 막을 수 있습니다.
다크 모드 구현: 깜박임 없는 최적의 방법
다크 모드는 제가 가장 크게 실패했던 부분입니다.
처음에는 v3 튜토리얼을 따라 전환 버튼을 만들었는데, 클릭할 때 페이지가 하얗게 번쩍인 뒤 검게 바뀌었습니다. 사용자는 눈이 부시다고 불평했습니다. 나중에야 이를 FOUC(flash of unstyled content)라고 하며 Next.js 서버 렌더링과 관련된 문제라는 걸 알았습니다.
v4에서는 다크 모드 설정도 달라졌습니다. v3의 darkMode: 'class'는 사라졌고 이제 class 전략이 기본이라 별도 설정이 필요 없습니다. 하지만 깜박임 문제는 남아 있어 next-themes 라이브러리로 해결해야 합니다.
먼저 설치합니다.
npm install next-themes
그다음 루트 레이아웃을 ThemeProvider로 감쌉니다.
// app/layout.tsx
import { ThemeProvider } from 'next-themes'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="zh-CN" suppressHydrationWarning>
<body>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
</ThemeProvider>
</body>
</html>
)
}
suppressHydrationWarning은 꼭 넣어야 합니다. next-themes가 클라이언트에서 <html>에 class="dark"를 추가하면 서버에서 렌더링한 내용과 달라져 React 경고가 발생하는데, 이 속성이 경고를 막습니다.
전환 버튼은 클라이언트 컴포넌트로 작성합니다.
'use client'
import { useTheme } from 'next-themes'
import { useEffect, useState } from 'react'
export function ThemeToggle() {
const [mounted, setMounted] = useState(false)
const { theme, setTheme } = useTheme()
useEffect(() => setMounted(true), [])
if (!mounted) return null // 서버 렌더링 불일치 방지
return (
<button
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
className="rounded-lg p-2 hover:bg-neutral-100 dark:hover:bg-neutral-800"
>
{theme === 'dark' ? '🌞' : '🌙'}
</button>
)
}
CSS 변수도 다크 모드에 맞게 조정합니다.
@theme {
--color-bg-primary: #ffffff;
--color-text-primary: #111827;
}
.dark {
--color-bg-primary: #111827;
--color-text-primary: #f9fafb;
}
또는 Tailwind 클래스에서 dark: 접두사를 바로 사용할 수 있습니다.
<div className="bg-white dark:bg-neutral-900 text-neutral-900 dark:text-neutral-50">
다크 모드에 자동 대응
</div>
다크 모드는 단순히 흑백을 뒤집는 기능이 아닙니다. 순수한 검정(#000000)은 너무 자극적이므로 진한 회색(#111827 또는 #1a1a1a)을 쓰고, 순백색 글자도 너무 밝으므로 #f9fafb 정도로 낮추는 편이 편안합니다. 다크 모드에서는 그림자가 잘 보이지 않으므로 반대로 처리해야 합니다.
// 라이트 모드: 아래쪽 그림자
<div className="shadow-lg dark:shadow-none dark:ring-1 dark:ring-neutral-800">
다크 모드에서는 그림자 대신 ring(테두리)을 사용하는 편이 더 좋습니다.
이미지는 다크 모드에서 과도하게 밝아 보일 수 있으므로 필터로 밝기를 낮출 수 있습니다.
.dark img {
filter: brightness(0.9);
}
이런 세부 사항까지 처리해야 다크 모드를 실제로 편안하게 사용할 수 있습니다. 기능 구현만 끝났다고 완료된 것이 아닙니다.
성능 최적화: 작고 빠른 CSS 만들기
앞서 말한 CSS 번들 500KB에서 50KB로의 감소는 과장이 아니라 실제 결과입니다.
v4는 JIT 모드가 기본으로 활성화되어 필요한 스타일만 생성하므로 이미 빠릅니다. 그래도 content 설정을 중심으로 더 최적화할 수 있습니다.
많은 사람이 다음과 같이 작성합니다.
// ❌ 스캔 범위가 너무 큼
content: [
'./**/*.{js,ts,jsx,tsx}',
]
이렇게 하면 node_modules, .next 같은 빌드 결과물까지 프로젝트 전체를 스캔해 시간을 낭비합니다. 범위를 정확히 지정하세요.
// ✅ 필요한 디렉터리만 스캔
content: [
'./app/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx}',
'./lib/**/*.{js,ts}',
]
제가 테스트했을 때 이렇게 바꾸자 개발 서버 시작 속도가 40% 빨라졌습니다.
동적 클래스명도 흔한 문제입니다.
// ❌ 이 클래스들은 purge로 제거됨
const colors = ['red', 'blue', 'green']
<div className={`bg-${colors[0]}-500`}>
Tailwind는 완전한 bg-red-500을 찾지 못하므로 프로덕션 빌드에서 클래스를 제거하고 화면의 스타일이 사라집니다. 완전한 클래스명을 쓰거나 다음처럼 매핑하세요.
// ✅ 완전한 클래스명 작성
const colorMap = {
red: 'bg-red-500',
blue: 'bg-blue-500',
green: 'bg-green-500',
}
<div className={colorMap[color]}>
또는 safelist로 강제로 보존할 수 있습니다.
// tailwind.config.ts
safelist: [
{
pattern: /bg-(red|blue|green)-500/,
},
]
다만 safelist를 지나치게 쓰면 CSS가 다시 커집니다. 가능한 경우 코드에 완전한 클래스명을 쓰고 불가피할 때만 safelist를 사용하세요.
v4는 CSS 압축 기능을 내장하고 프로덕션 빌드에서 자동으로 활성화합니다. 더 최적화하고 싶다면 cssnano를 설정할 수 있습니다.
// postcss.config.js
module.exports = {
plugins: {
tailwindcss: {},
...(process.env.NODE_ENV === 'production' ? { cssnano: {} } : {}),
},
}
번들 크기 모니터링도 놓치기 쉽습니다. 저는 @next/bundle-analyzer로 정기적으로 확인합니다.
npm install --save-dev @next/bundle-analyzer
// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
})
module.exports = withBundleAnalyzer({
// 기타 설정
})
ANALYZE=true npm run build를 실행하면 시각화 보고서가 생성되어 어떤 패키지가 큰지 바로 알 수 있습니다.
Netflix의 사례도 흥미롭습니다. Top 10 페이지의 CSS가 6.5KB에 불과합니다. 실제로 사용하는 스타일만 남기고 극도로 줄였기 때문입니다. 꼭 그 정도까지 갈 필요는 없지만 필요한 것만 쓰라는 접근은 참고할 만합니다.
한번은 코드 리뷰 중 동료가 @heroicons/react 전체를 가져왔지만 실제로는 아이콘 두 개만 쓰는 것을 발견했습니다. 필요한 항목만 가져오도록 바꾸자 번들 크기가 곧바로 200KB 줄었습니다. 작은 일이지만 쌓이면 큰 차이를 만듭니다.
성능 최적화는 일회성 작업이 아니라 지속적인 과정입니다. 배포 전마다 번들을 분석하는 습관을 들여야 CSS 크기를 통제할 수 있습니다.
v3에서 v4로 마이그레이션: 안정적인 업그레이드 가이드
아직 v3를 사용한다면 지금 업그레이드해도 될까요? 상황에 따라 다릅니다.
새 프로젝트라면 v4로 바로 시작해도 됩니다. 기존 프로젝트는 변경 비용을 평가해야 합니다. v4에는 단순히 npm install만으로 끝나지 않는 호환성 파괴 변경이 있습니다.
가장 큰 변화는 설정 파일입니다. v3의 tailwind.config.js 설정을 global.css로 옮겨야 합니다.
/* 이전 tailwind.config.js */
module.exports = {
theme: {
extend: {
colors: {
primary: '#3b82f6',
},
},
},
}
/* 현재 globals.css */
@theme {
--color-primary: #3b82f6;
}
사용자 정의 유틸리티 클래스도 바뀌었습니다. 이전의 @layer utilities 대신 이제 @utility를 사용합니다.
/* v3 */
@layer utilities {
.text-balance {
text-wrap: balance;
}
}
/* v4 */
@utility text-balance {
text-wrap: balance;
}
컴포넌트 클래스가 더 이상 variant를 지원하지 않는다는 숨은 함정도 있습니다. 예전에는 다음과 같이 작성할 수 있었습니다.
/* v3에서는 가능 */
@layer components {
.btn {
@apply px-4 py-2 rounded;
}
}
/* 이후 hover:btn, dark:btn 같은 variant 사용 */
v4에서는 hover:btn 같은 형태가 오류를 일으킵니다. utility로 바꾸거나 React 컴포넌트로 캡슐화해야 합니다.
테두리 색상 문제도 있습니다. 앞서 말했듯 v4의 기본값은 border-color: currentColor이므로 많은 테두리가 “사라진” 것처럼 보입니다. 프로젝트 전체에서 border를 찾아 색상이 없는 항목에 border-neutral-300을 추가하세요.
// v3: 테두리가 자동으로 회색
<div className="border"></div>
// v4: 테두리가 글자 색상을 따르므로 명시적으로 지정
<div className="border border-neutral-300"></div>
단계별 마이그레이션을 권합니다.
- 1단계: v4를 설치하고 개발 환경을 실행해 눈에 띄는 스타일 문제가 있는지 확인합니다.
- 2단계: 전체 프로젝트에서
@layer를 찾아@utility또는@theme로 바꿉니다. - 3단계:
border를 찾아 색상이 없는 항목에 색상을 추가합니다. - 4단계: 설정 파일의 theme를 CSS로 옮기며 단계별로 테스트합니다.
- 5단계: purge로 제거된 동적 클래스명이 있다면 safelist로 보완합니다.
프로젝트 규모에 따라 전체 과정은 반나절에서 하루 정도 걸립니다. 한 번에 모두 바꾸려 하지 말고 나눠 적용해야 문제가 생겼을 때 쉽게 롤백할 수 있습니다.
shadcn/ui나 다른 컴포넌트 라이브러리를 쓴다면 먼저 v4 지원 여부를 확인하세요. 저는 라이브러리가 아직 대응하지 않았는데 먼저 업그레이드했다가 컴포넌트 스타일이 전부 깨진 적이 있습니다.
v4는 전반적으로 좋은 선택이지만 반드시 업그레이드해야 하는 것은 아닙니다. v3가 잘 작동한다면 서두르지 않아도 됩니다. 기술 부채는 언젠가 갚아야 하지만 적절한 시점을 선택할 수 있습니다.
결론
클래스가 23개였던 버튼에서 <Button variant="primary">로 오기까지 거의 2년이 걸렸습니다.
Tailwind와 Next.js의 조합은 강력하지만 아무 준비 없이도 잘 쓸 수 있는 것은 아닙니다. v4의 변화는 처음에는 부담스러워 보여도 설정을 단순하게 하고 성능과 개발 경험을 개선하는 올바른 방향입니다.
컴포넌트 캡슐화, 테마 설정, 다크 모드, 성능 최적화를 전부 한꺼번에 적용할 필요는 없습니다. 지금 겪는 문제를 해결할 방법 몇 가지부터 시도하세요. 프로젝트 전체를 한 번에 리팩터링하면 지나치게 힘들고 문제도 생기기 쉽습니다.
먼저 컴포넌트 캡슐화부터 시작하기를 권합니다. 오후 한나절을 들여 자주 쓰는 Button, Card, Input을 만들고 cva로 variant를 관리하세요. 위험은 작고 효과는 즉시 나타납니다. 그다음 다크 모드와 테마 사용자 정의를 검토하면 됩니다.
v4 업그레이드도 서두르지 마세요. 커뮤니티 생태계가 성숙했는지, 사용하는 컴포넌트 라이브러리가 지원하는지, 직접 손볼 시간이 있는지 확인하세요. 기술은 새로울수록 좋은 것이 아니라 상황에 잘 맞을수록 좋습니다.
Tailwind를 쓰며 비슷한 문제를 겪었거나 더 나은 방법이 있다면 댓글로 나눠주세요. 여러분의 방법이 제 방법보다 더 우아할 수도 있습니다.
글에서 소개한 코드 예제는 모두 GitHub에 올려두었습니다(글 끝의 링크 참고). 바로 사용할 수 있으며 문제가 있다면 Issues에서 알려주세요.
읽는 데서 끝내지 말고 직접 실행해 보세요. 코드를 돌려보지 않으면 영원히 배울 수 없습니다.
Next.js + Tailwind CSS v4 전체 설정 절차
제로 설정부터 컴포넌트 캡슐화, 성능 최적화, 다크 모드까지 진행하는 전체 단계
⏱️ Estimated time: 3 hr
- 1
Step 1: Tailwind v4 기본 설정
v4의 변화:
• 설정 파일이 사라졌습니다(제로 설정 철학).
• 사용자 정의 설정은 CSS 변수를 사용하는 global.css로 옮겨졌습니다.
• 기반을 Rust로 다시 작성해 속도가 5배 빨라졌습니다.
global.css 설정:
```css
@theme {
--color-primary: #3b82f6;
--color-secondary: #8b5cf6;
--font-sans: 'Inter', sans-serif;
}
```
장점:
• CSS 변수를 바꾸면 핫 리로드에 즉시 반영됩니다.
• 디자이너도 이해할 수 있습니다.
• 개발 서버를 재시작할 필요가 없습니다.
핵심: v4의 철학은 '제로 설정'이며 프로젝트 파일을 자동으로 스캔해 바로 사용할 수 있습니다. - 2
Step 2: 클래스명 폭증 문제 해결
문제: 클래스명이 너무 길어 한 줄에 23개나 몰려 있습니다.
해결책: 컴포넌트 캡슐화
cva로 variant 관리:
```tsx
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'
const buttonVariants = cva(
'inline-flex items-center justify-center rounded-md',
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground',
destructive: 'bg-destructive text-destructive-foreground',
},
size: {
default: 'h-10 px-4 py-2',
sm: 'h-9 px-3',
lg: 'h-11 px-8',
},
},
}
)
export function Button({ variant, size, className, ...props }) {
return (
<button
className={cn(buttonVariants({ variant, size }), className)}
{...props}
/>
)
}
```
효과: 23개 클래스명을 variant, size, className 세 개로 줄입니다.
핵심: 오후 한나절 동안 Button, Card, Input을 컴포넌트로 만들고 cva로 variant를 관리하세요. - 3
Step 3: 성능 최적화(500KB→50KB)
최적화 방법:
1. purge 설정 사용:
```js
// tailwind.config.js(v3)
module.exports = {
content: ['./app/**/*.{js,ts,jsx,tsx}'],
// 실제 사용하는 클래스만 포함
}
```
2. 필요한 항목만 가져오기:
```tsx
// 라이브러리 전체를 가져오지 않기
import { Button } from '@/components/ui/button'
```
3. 동적 클래스명 피하기:
```tsx
// ❌ 잘못된 예: 동적 클래스명은 purge가 감지하지 못함
const color = `bg-${theme}-500`
// ✅ 올바른 예: 완전한 클래스명 사용
const color = theme === 'blue' ? 'bg-blue-500' : 'bg-red-500'
```
4. JIT 모드 사용(v3):
```js
module.exports = {
mode: 'jit', // 필요할 때 생성
}
```
효과: 500KB에서 50KB로 90% 줄어듭니다. - 4
Step 4: 다크 모드 설정
v4에서는 darkMode 설정 없이 next-themes를 사용합니다.
next-themes 설치:
```bash
npm install next-themes
```
ThemeProvider 설정:
```tsx
'use client'
import { ThemeProvider } from 'next-themes'
export function Providers({ children }) {
return (
<ThemeProvider attribute="class" defaultTheme="system">
{children}
</ThemeProvider>
)
}
```
dark: 접두사 사용:
```tsx
<div className="bg-white dark:bg-gray-900 text-black dark:text-white">
콘텐츠
</div>
```
핵심:
• v4는 dark: 접두사를 자동 지원합니다.
• next-themes로 테마를 관리합니다.
• darkMode 설정이 필요 없습니다.
FAQ
Tailwind v4에서는 무엇이 달라졌나요?
1. 설정 파일이 사라졌습니다(제로 설정 철학).
• tailwind.config.js가 선택 사항이 되었습니다.
• 프로젝트 파일을 자동으로 스캔해 바로 사용할 수 있습니다.
2. 사용자 정의 설정이 global.css로 옮겨졌습니다.
• CSS 변수로 테마 색상, 간격, 글꼴을 정의합니다.
• CSS 변수를 바꾸면 핫 리로드에 즉시 반영됩니다.
3. 기반을 Rust로 다시 작성했습니다.
• 속도가 5배 빨라졌습니다.
• 콜드 스타트가 8초에서 2초 미만으로 줄었습니다.
4. 다크 모드가 단순해졌습니다.
• darkMode 설정이 필요 없습니다.
• dark: 접두사를 자동 지원합니다.
장점:
• 설정이 간단합니다.
• 속도가 빠릅니다.
• 핫 리로드가 빠릅니다.
주의: v4는 아직 beta 단계이므로 프로덕션 환경에서는 안정 버전을 기다리는 편이 좋습니다.
클래스명 폭증 문제는 어떻게 해결하나요?
해결책: 컴포넌트 캡슐화
cva로 variant 관리:
```tsx
import { cva } from 'class-variance-authority'
const buttonVariants = cva(
'inline-flex items-center justify-center',
{
variants: {
variant: {
default: 'bg-primary',
destructive: 'bg-destructive',
},
size: {
default: 'h-10 px-4',
sm: 'h-9 px-3',
},
},
}
)
export function Button({ variant, size, className, ...props }) {
return (
<button
className={cn(buttonVariants({ variant, size }), className)}
{...props}
/>
)
}
```
효과:
• 23개 클래스명이 세 개로 줄어듭니다.
• variant를 한곳에서 관리합니다.
• 변경하기 쉽습니다.
권장: 오후 한나절 동안 Button, Card, Input을 컴포넌트로 만들고 cva로 variant를 관리하세요.
Tailwind 성능을 어떻게 최적화하나요(500KB→50KB)?
1. purge 설정 사용:
```js
module.exports = {
content: ['./app/**/*.{js,ts,jsx,tsx}'],
}
```
2. 필요한 항목만 가져오기:
```tsx
import { Button } from '@/components/ui/button'
```
3. 동적 클래스명 피하기:
```tsx
// ❌ 잘못된 예
const color = `bg-${theme}-500`
// ✅ 올바른 예
const color = theme === 'blue' ? 'bg-blue-500' : 'bg-red-500'
```
4. JIT 모드 사용(v3):
```js
module.exports = {
mode: 'jit',
}
```
효과: 500KB에서 50KB로 90% 줄어듭니다.
핵심: 실제 사용하는 클래스만 포함하고 동적 클래스명을 피하세요.
Tailwind v4의 다크 모드는 어떻게 설정하나요?
설치:
```bash
npm install next-themes
```
설정:
```tsx
'use client'
import { ThemeProvider } from 'next-themes'
export function Providers({ children }) {
return (
<ThemeProvider attribute="class" defaultTheme="system">
{children}
</ThemeProvider>
)
}
```
사용:
```tsx
<div className="bg-white dark:bg-gray-900">
콘텐츠
</div>
```
핵심:
• v4는 dark: 접두사를 자동 지원합니다.
• next-themes로 테마를 관리합니다.
• darkMode 설정이 필요 없습니다.
주의: html 태그에 suppressHydrationWarning을 추가해야 합니다.
Tailwind v4로 업그레이드해야 하나요?
장점:
• 속도가 5배 빠릅니다.
• 설정이 간단합니다.
• 핫 리로드가 빠릅니다.
단점:
• 아직 beta 단계입니다.
• 생태계가 충분히 성숙하지 않았을 수 있습니다.
• 마이그레이션에 시간이 듭니다.
권장:
• 새 프로젝트: v4를 시도해도 좋습니다.
• 기존 프로젝트: 안정 버전이 나온 뒤 업그레이드하세요.
• 확신이 없다면: v3를 쓰면서 v4가 안정될 때까지 기다리세요.
핵심: 기술은 새로울수록 좋은 것이 아니라 적합할수록 좋습니다. 커뮤니티 생태계가 성숙했는지, 사용 중인 컴포넌트 라이브러리가 지원하는지, 직접 손볼 시간이 있는지 살펴보세요.
Tailwind 테마는 어떻게 관리하나요?
global.css에서 정의:
```css
@theme {
--color-primary: #3b82f6;
--color-secondary: #8b5cf6;
--font-sans: 'Inter', sans-serif;
}
```
사용:
```tsx
<div className="bg-primary text-primary-foreground">
콘텐츠
</div>
```
장점:
• CSS 변수를 바꾸면 핫 리로드에 즉시 반영됩니다.
• 디자이너도 이해할 수 있습니다.
• 개발 서버를 재시작할 필요가 없습니다.
권장:
• 컴포넌트 캡슐화부터 시작하세요.
• 오후 한나절 동안 Button, Card, Input을 만드세요.
• cva로 variant를 관리하세요.
• 그다음 테마 사용자 정의를 검토하세요.
4분 읽기 · 게시일: 2025년 12월 20일 · 수정일: 2026년 9월 8일
Next.js 완전 가이드
검색으로 들어왔다면 같은 시리즈의 이전 글이나 다음 글로 이동하는 것이 가장 빠릅니다.
이전
Next.js 다크 모드 구현: next-themes 완벽 가이드
Next.js에서 next-themes로 깜빡임 없는 다크 모드를 구현하는 방법을 설명합니다. 설정 원리, 전체 코드, 시스템 테마 연동과 자주 발생하는 오류 해결법까지 다룹니다.
45편 중 39편
다음
Next.js App Router + shadcn/ui: 서버와 클라이언트 컴포넌트 혼용 가이드
Next.js App Router에서 Server Components와 Client Components를 올바르게 함께 사용하는 방법을 설명하고, shadcn/ui 통합 실전, 데이터 흐름 설계, 자주 발생하는 오류 해결법과 성능 최적화 팁을 다룹니다.
45편 중 41편



댓글
GitHub로 로그인하여 댓글을 남기세요