
모노레포 기반으로 여러 프로젝트를 운영하는 과정에서, 공통으로 사용할 디자인 시스템의 필요성이 커져 별도 패키지로 구성하게 되었는데요. 기본적인 토큰(foundation)은 CSS로 구축했고, 컴포넌트는 각 프로젝트에서 사용하는 방식과 동일하게 TailwindCSS 기반의 className으로 스타일링했습니다.
이때 디자인 시스템 패키지에는 TailwindCSS를 설치하지 않고, 각 프로젝트에 설치된 Tailwind가 내부 className을 스캔해 스타일을 생성해줄 것이라 기대했습니다.
하지만 동일한 컴포넌트를 사용하는 두 프로젝트에서 한쪽은 정상적으로 스타일이 적용되었고, 다른 한쪽에서는 Tailwind 클래스가 누락되는 문제가 발생했습니다. 이 예상치 못한 차이를 분석하는 과정에서 여러 원인과 구조적인 제약을 확인하게 되었고, 제가 겪었던 경험에 대해서 정리해보고자 합니다!
모노레포는 다음과 같은 형태로 구성되어 있습니다!
(이해를 돕기 위한 참고용 구조입니다.)
├─ apps
│ ├─ project-a
│ ├─ project-b
│ └─ ...
│
└─ packages
├─ design-system
├─ components
├─ hooks
└─ utils
각 프로젝트는 apps 디렉토리 아래에 위치하며, packages에는 여러 프로젝트에서 공통으로 사용할 수 있는 패키지들(components, hooks, utils 등) 이 존재하는 구조입니다.
이 중 디자인 시스템은 다음과 같은 형태로 구성되어 있는데요.
빌드 결과물은 dist로 생성되며, 각 컴포넌트는 반드시 index.ts에서 export 해주어야합니다.
dist 란?
- 빌드된 결과물이 담기는 디렉토리를 말합니다.
- 개발 시 작성하는 토드틑 src 내부에 있지만 이 코드를 그대로 다른 프로젝트에서 사용하기 어렵습니다. (Typescrip, JSX, 내부 경로 등 그대로는 브라우저나 외부 프로젝트가 이해하지 못합니다ㅠ)
- 그래서 빌드 도구(tsup) 을 통해 JS로 변환하고 번들링한 최종 산출물을 생성하는데, 이 최종 산출물이 저장되는 폴더가 dist입니다!
- 즉, 외부 프로젝트에서 실제 가져다 쓰는 배포용 코드가 담긴 폴더 라고 할 수 있습니다
packages
└─ design-system
├─ dist
├─ node_modules
├─ src
│ ├─ component
│ ├─ contents
│ ├─ theme
│ ├─ types
│ ├─ index.ts
│ └─ index.css
├─ package.json
├─ tsconfig.json
└─ tsup.config.ts
디자인 시스템을 적용하고 발생한 문제는 동일한 컴포넌트를 사용했는데 프로젝트마다 TailwindCSS 클래스 적용 결과가 다르게 나타난다 는 점이었습니다.
A 프로젝트에서는 디자인 시스템에서 정의한 Tailwind 클래스가 문제없이 적용되었지만, B 프로젝트에서는 동일한 컴포넌트임에도 일부 클래스가 누락 되어 스타일이 깨지는 현상이 발생했습니다.
| A 프로젝트(적용 O) | B 프로젝트(적용 X) |
|---|---|
![]() | ![]() |
같은 디자인 시스템을 import하여 사용하는데도 불구하고 특정 클래스만 적용되지 않는 상황이 반복되었습니다...😭
처음에는 Tailwind 설정 문제로 추측했지만 두 프로젝트 모두 동일한 설정을 가지고 있었기 때문에 단순한 설정 이슈로는 보이지 않았습니다. 겉으로 보기에는 환경 구성도 동일했기 때문에 문제의 원인을 바로 파악하기 어려웠습니다.
문제의 원인을 파악하기 위해 여러 방향에서 하나씩 검증을 진행했습니다. 처음에는 단순한 설정 문제라고 생각했지만 확인 과정이 반복될수록 디자인 시스템 패키지와 tailwind의 동작 방식이 예상과 다르다는 점을 알 수 있었습니다!
GPT의 도움을 받아 가장 처음 수정했던 부분은 tailwind의 content 설정이었습니다. content는 해당 경로에 존재하는 파일을 스캔하여 className을 추출하고 그 결과를 기반으로 최종 CSS를 생성합니다.
따라서, 디자인 시스템 내부 파일이 스캔 대상에서 누락되고 있다면 일부 클래스가 생성되지 않는 문제가 발생할 수 있겠다는 가설이었고, 디자인 시스템 컴포넌트가 있는 경로를 아래와 같이 추가해주었습니다.
content: [
'./app/**/*.{jsx,tsx,mdx}',
'./components/**/*.{jsx,tsx,mdx}'
// 디자인 시스템 경로 추가
'../packages/design-system/components/**/*.{js,ts,jsx,tsx}'
],
...
하지만 결과는 동일했습니다. 애초에 설정 자체는 두 프로젝트 모두 동일하게 되어있었기 때문에 단순히 content 범위 오류로 발생한 문제는 아니었습니다.
(그리고 모노레포 구조 특성상 app 디렉토리 밖으로 나가면 이론상 프로젝트를 벗어나가는건데 packages에 접근이 가능한지도 정확하지 않았습니다 😇)
다음으로 확인한 부분은 두 프로젝트가 사용하고 있는 Next.js 버전 차이었습니다.
A프로젝트는 15.3.5, B프로젝트는 15.5.5 버전을 사용하고 있었고 혹시나 두 버전 사이에 TailwindCSS 처리 방식이 달라졌을 가능성을 의심했습니다.
테스트를 위한 신규 프로젝트를 15.3.5 버전으로 설치한 결과 동일하게 디자인 시스템 스타일이 깨지는 문제가 발생하는 것을 확인할 수 있었습니다. 따라서 Next.js 자체의 문제는 아니라는 결론을 내릴 수 있었습니다.
즉, 문제의 핵심은 버전이나 설정 문제가 아닌 tailwind의 스캔 범위 또는 빌드 타이닝에 있다는 방향으로 좁혀졌습니다.
문제의 원인을 보다 명확히 확인하기 위해 TailwindCSS가 실제로 어떤 클래스를 빌드하는지 직접 확인했습니다.
Next.js는 빌드 시 Tailwind가 스캔한 className을 기반으로 최종 CSS 파일 (layout.css) 을 생성 하는데, 만약 디자인 시스템 컴포넌트가 스캔되지 않았다면 해당 클래스는 layout.css에 생성되지 않을 것입니다.
이를 확인하기 위해 A 프로젝트와 B 프로젝트 각각의 빌드 결과물을 비교했습니다. 그 결과 다음과 같은 사실을 확인했습니다.
| A 프로젝트 | B 프로젝트 |
|---|---|
![]() | ![]() |
mt-7, text-4xl 등 디자인 시스템에서 사용한 클래스가 모두 존재했습니다.이 차이를 확인하면서 문제의 원인은 더욱 명확해졌습니다. 디자인 시스템 패키지 내부의 파일이 Tailwind 스캔 대상에 포함되지 않았기 때문에, 프로젝트마다 생성되는 CSS가 달라지고, 그 결과 컴포넌트가 정상적으로 렌더링되지 않는 구조적 문제 가 발생한 것입니다.
결론적으로 문제의 핵심은 TailwindCSS가 스타일을 생성하는 시점에 packages/design-system 내부의 className을 스캔하지 못한다는 것이었습니다.
TailwindCSS는 content 옵션에 포함된 경로만 기준으로 실제 사용된 className을 스캔해 layout.css 에 필요한 유틸리티 클래스를 생성합니다. 하지만 monorepo 구조에서 프로젝트 밖의 패키지(packages/design-system)는 content 스캔 범위에 포함되지 않는다 는 것을 알 수 있었습니다.
정리하자면,
즉, 특정 프로젝트만 깨진 것이 아니라, Tailwind가 아예 해당 클래스를 빌드하지 않았기 때문에 발생한 구조적 문제였습니다.
지금의 문제를 해결할 수 있는 방법은 3가지로 추릴 수 있었습니다.
1. CSS 사용
➡️ TailwindCSS 의존도를 낮추고 디자인 시스템 패키지 내부에서 직접 CSS를 정의해 제공하는 방법입니다. 이렇게 하면 각 프로젝트의 Tailwind 스캠 여부와 무관하에 안정적으로 스타일이 적용되기 때문에 가장 확실한 해결책이라고 할 수 있습니다. 다만, CSS 파일 관리에 대해서 팀원들과 충분한 대화가 필요합니다. (컨벤션 등...)
2. tailwind.config.js content 경로 추가
➡️ Tailwind 가 스타일을 생성하려면 해당 경로에 있는 파일들을 스캔할 수 있어야하기 때문에 가장 논리적인 정석 방법이라고 할 수 있습니다. 하지만, 모노레포 구조에서 어떻게 적용해야하는지 그 방법에 대해서 찾아야하고 프로젝트마다 설정을 관리해야한다는 부담이 있습니다.
3. 디자인 시스템에 tailwindCSS 라이브러리 설치
➡️ 디자인 시스템이 자체적으로 Tailwind를 기반으로 스타일을 빌드할 수 있어 프로젝트 환경에 의존하지 않아도 된다는 장점이 있지만, 프로젝트에서 사용할 때 결국 Tailwind 라이브러리가 중복으로 로드된다는 점, 스타일이 충돌될 수 있다는 점에서 위험 부담이 있습니다.
쉽게 해결할 수 있는 문제인 줄 알았는데 꼬박 하루 걸려 원인을 발견할 수 있었습니다 😅 지금 와서 다시 돌이켜보면 처음부터 tailwind의 빌드 구조와 스캔 방식 같은 근본적인 원리를 짚고 들어갔다면 훨씬 빠르게 해결할 수 있지 않았을까 하는 생각이 들었습니다. 그래도 덕분에 평소 당연하게 사용하던 tailwind 가 어떤 식으로 동작하는지 깊에 이해할 수 있었던 좋은 경험이었습니다.
만약 저처럼 모노레포에서 tailwind기반의 디자인 시스템을 구축하며 비슷한 문제를 겪은 분이 있다면 경험이나 해결 과정을 함께 공유해봐요🙌 🏻