💣 pnpm + Turborepo 기반 Tailwind 중심 Design System 구축
이번 작업의 핵심은 단순히 모노레포를 구성하는 것이 아니라, Tailwind 기반 디자인 시스템을 공용 패키지로 분리하고, 각 앱이 이를 import만 해서 동일한 스타일을 사용하도록 만드는 구조 설계였습니다.
1. 전체 설계 의도
1. Tailwind를 중심으로 한 설계 -> 단순히 Tailwind를 사용하는 것이 아니라, 디자인 시스템 자체를 Tailwind 유틸리티 구조 위에서 일관되게 관리할 수 있도록 만드는 것이 목적.
2. 디자인 토큰과 앱 공통 스타일의 역할의 명확한 분리 -> color, typography, shadow처럼 디자인 규칙에 해당하는 영역과 실제 앱에서 사용하는 레이아웃이나 공통 래퍼 같은 앱 단위의 스타일을 한 파일 안에 섞이지 않도록 하는 것이 목표.
이를 위해 패키지를 두 개로 나누었습니다.
먼저, @compasser/tailwind-config는 디자인 유틸을 정의하는 전용 패키지입니다. (compasser는 프로젝트 이름입니다.)
이곳에서는 색상, 타이포그래피, 쉐도우 등과 같은 커스텀 유틸을 정의하고, Tailwind가 이를 인식할 수 있도록 연결하는 역할을 담당합니다. 즉, 디자인 규칙을 선언하는 영역입니다.
다음으로, design-system 패키지는 앱 공통 스타일을 담당합니다.
실제 root 토큰 값을 정의하고, 공통 wrapper나 page 레이아웃 같은 앱 전반에 적용되는 스타일을 제공합니다. 또한 className 충돌을 정리하기 위한 cn() 유틸도 이곳에 포함시켰습니다.
tailwind-config는 유틸 선언 전용 영역으로, design-system은 앱 공통 스타일과 실제 토큰 값을 관리하는 영역으로 나눔.
디자인 규칙과 앱 레벨 스타일의 책임을 명확하게 나누는 구조로 설계
2. pnpm 기반 모노레포 구성
🗝️ Workspace 설정
packages:
- "apps/*"
- "packages/*"
전체 구조는 다음과 같습니다.
repo/
│
├─ apps/
│ ├─ customer/
│ │ ├─ app/
│ │ │ ├─ layout.tsx
│ │ │ ├─ page.tsx
│ │ │ ├─ login/
│ │ │ │ └─ page.tsx
│ │ │ └─ orders/
│ │ │ └─ page.tsx
│ │ │
│ │ ├─ domains/
│ │ │ ├─ auth/
│ │ │ │ ├─ LoginForm.tsx
│ │ │ │ ├─ useLogin.ts
│ │ │ │ ├─ api.ts
│ │ │ │ └─ types.ts
│ │ │ │
│ │ │ ├─ order/
│ │ │ │ ├─ OrderList.tsx
│ │ │ │ ├─ useOrders.ts
│ │ │ │ └─ api.ts
│ │ │ │
│ │ │ └─ cart/
│ │ │
│ │ ├─ shared/
│ │ │ ├─ components/
│ │ │ ├─ hooks/
│ │ │ ├─ lib/
│ │ │ └─ utils/
│ │ │
│ │ ├─ styles/
│ │ ├─ next.config.ts
│ │ ├─ tailwind.config.ts
│ │ ├─ tsconfig.json
│ │ └─ package.json
│ │
│ └─ owner/
│ ├─ app/
│ ├─ domains/
│ │ ├─ product-management/
│ │ ├─ order-management/
│ │ └─ dashboard/
│ ├─ shared/
│ ├─ tailwind.config.ts
│ └─ package.json
│
├─ packages/
│ ├─ design-system/
│ │ ├─ src/
│ │ │ ├─ components/
│ │ │ │ ├─ Button/
│ │ │ │ ├─ Input/
│ │ │ │ ├─ Modal/
│ │ │ │ └─ index.ts
│ │ │ │
│ │ │ ├─ libs/
│ │ │ └─ index.ts
│ │ │
│ │ ├─ tailwind.preset.ts
│ │ ├─ tsconfig.json
│ │ └─ package.json
│ │
│ ├─ typescript-config/
│ │ ├─ base.json
│ │ ├─ next.json
│ │ └─ package.json
│ │
│ └─ ui/
│ ├─ .storybook/
│ ├─ src/
│ └─ package.json
│
├─ turbo.json
├─ pnpm-workspace.yaml
├─ package.json
└─ tsconfig.base.json
📄 Turborepo 설정
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "dist/**"]
},
"dev": {
"cache": false
},
"lint": {},
"type-check": {}
}
}
{
"name": "compasser-fe",
"private": true,
"scripts": {
"dev": "turbo run dev --parallel",
"build": "turbo run build",
"lint": "turbo run lint",
"type-check": "turbo run type-check"
}
}
🌐Apps 생성
cd apps
pnpm create next-app customer --ts --app --tailwind --eslint
pnpm create next-app owner --ts --app --tailwind --eslint
각 앱은 완전히 독립적인 Next.js 앱으로 구성됩니다.
📘packages 구조
packages/
tailwind-config/
theme.css
design-system/
style.css
src/lib/utils.ts
tailwind-config (theme.css)
1. theme.css의 역할은 디자인 유틸 정의 + Tailwind 브릿지 입니다. (아래는 요약한 형태입니다)
@import url("Pretendard CDN");
@import "tailwindcss";
/* Color utility */
@utility bg-primary {
background-color: var(--color-primary);
}
/* Typography */
@utility head1 {
font-size: 2.4rem;
font-weight: 700;
line-height: 140%;
}
/* Shadow */
@utility common-shadow {
box-shadow: 0 4px 20px rgba(0,0,0,0.05);
}
/* Tailwind bridge */
@theme {
--color-primary: var(--color-primary);
}
2. design-system (style.css)
@import "@compasser/tailwind-config/theme.css";
@source "./src";
:root {
--color-primary: #2563eb;
--color-text: #111111;
--color-gray-100: #f3f4f6;
--font-sans: "Pretendard", sans-serif;
}
✨ cn() 유틸 구현 이유
Tailwind 기본 병합만으로는 커스텀 유틸(head1, bg-primary 등)이 같은 그룹으로 인식되지 않아 충돌이 정리되지 않았습니다. 그래서 extendTailwindMerge로 그룹을 확장했습니다.
import { clsx } from "clsx";
import { extendTailwindMerge } from "tailwind-merge";
const twMerge = extendTailwindMerge({
classGroups: {
typography: ["head1", "head2", "body1", "caption"],
color: ["bg-primary", "bg-gray-100", "text-default", "text-inverse"],
shadow: ["common-shadow"]
}
});
export function cn(...inputs) {
return twMerge(clsx(inputs));
}
앱에서의 사용 흐름
1. design-system/style.css import
2. 최상위 Layout에 app-wrapper 적용
3. 각 페이지 루트에 page 클래스 적용
4. 컴포넌트 내부에서 cn() 사용
5. head2, bg-primary, text-default 등 커스텀 유틸 사용
총정리
디자인 시스템 구조는 디자이너께서 제공해주신 디자인 시스템에 맞춰서 설계를 했습니다. Tailwind를 단순 스타일 도구로 사용하는 것이 아니라, 디자인 토큰 시스템과 결합한 구조로 확장한 설계입니다. 이 구조를 기반으로 하면 앱이 늘어나도 스타일 일관성을 유지할 수 있고, 디자인 변경 시 단일 패키지 수정만으로 전체 앱에 반영할 수 있습니다.