카테고리 없음

Next.js 모노레포 디자인 시스템 구축

minnote29 2026. 2. 16. 01:34

💣 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를 단순 스타일 도구로 사용하는 것이 아니라, 디자인 토큰 시스템과 결합한 구조로 확장한 설계입니다. 이 구조를 기반으로 하면 앱이 늘어나도 스타일 일관성을 유지할 수 있고, 디자인 변경 시 단일 패키지 수정만으로 전체 앱에 반영할 수 있습니다.