문제상황 인식
매번 디자인을 생각하면서 컴포넌트를 만들기 너무 귀찮아서.. 만드는 제품에 대한 일관성 있는 디자인이 필요해보였다.
하지만 함께 협업하는 프론트엔드 개발 참여자들의 기술 스택이 서로 달라서 항상 스타일링 라이브러리 선택시 성능이나 DX 보다는 “모든 사람이 사용해 본 라이브러리” 에 초점이 맞춰지는 것 같아서 개인적으로는 안타까웠던 적이 많았다. (러닝 커브도 DX라고 해야하나…?)
예를 들어
- A : tailwindcss, shadcn/ui, next.js
- B : Emotion, charka-ui, vite
또한 CSR 환경과 SSR 환경에서 CSS-in-JS 방식은 성능적인 부분에서 약간의 차이가 발생하기 때문에, 두 환경 모두 대응할 수 있어야 한다.
왜 SSR(특히 Next.js App Router)에서는 런타임 CSS-in-JS를 지양할까?
[Next.js] App Router에서의 CSS-in-JS 사용과 한계
내가 생각한 가장 큰 이유는 다음과 같다.
- 런타임 오버헤드: CSS-in-JS는 자바스크립트 실행 시점에 스타일을 계산하고 DOM에 주입한다. 이는 브라우저의 메인 스레드에 부담을 주게 된다.
- Streaming 및 RSC 호환성: Next.js의 서버 컴포넌트(RSC)는 서버에서 스타일을 미리 계산해야 하는데, 런타임 CSS-in-JS 라이브러리들은 React Context API를 기반으로 동작하기 때문에 서버 컴포넌트에서 직접 사용이 불가능하거나
'use client'를 강제하게 된다. - 스타일 주입 시점: 서버에서 생성된 정적 HTML이 먼저 표시된 후, JS가 로드되어 스타일을 계산하는 과정에서 스타일이 뒤늦게 적용되어 화면이 깜빡이는 현상(FOUC)이 발생할 수 있다.
그렇다면 CSR과 SSR 에서 큰 이슈없이 사용할 수 있는 라이브러리 중에 Emotion과 styled-components는 제외하고 남은 라이브러리는
- TailwindCSS
- SCSS
- vanilla-extract
이 정도의 CSS 라이브러리가 살아남았다!
최종 선택 : vanilla-extract
next.js를 공부하는 개발자들에게는 TailwindCSS가 그렇게 낯설지 않겠지만, 아직 react + vite에 대해 공부하고 있다면 TailwindCSS를 사용하는데에는 아직까지도 많은 러닝커브가 요구된다고 생각한다.
또한, 직접 디자인시스템을 만들고, 라이브러리를 만든다 한들, “shadcn/ui 라는 강력한 라이브러리와 그들의 생태계를 이길 수 있을까?” 라는 생각이 들었다.
따라서 TailwindCSS를 배제하면 SCSS와 vanilla-extract 정도가 남았다.
여기서 나의 목표는 shadcn과 같은 간편함과 charka-ui 처럼 props로 간단하게 스타일링 할 수 있도록 하는 방법을 이용해서 최대한 개발자경험(DX)가 좋은 라이브러리를 만드는 것이기 때문에 최종적으로 vanillla-extract를 선택하게 되었다.
vanilla-extract를 선택한 이유는 다음과 같다.
- Zero-runtime: 빌드 타임에 정적 CSS 파일로 추출되어 SSR 성능 이슈가 없다.
- Type-safe: TypeScript로 스타일을 작성하기 때문에 강력한 자동완성과 타입 추론을 제공한다. (DX의 정점!)
- Recipe API:
@vanilla-extract/recipes를 이용하면 chakra-ui와 유사한 Variant 기반 디자인 시스템을 가장 깔끔하게 구현할 수 있다.
안써봐서 잘 모른다. 차차 알아가보자…….
개발환경 선택
개발을 진행할 때 성능과 참고할 레퍼런스를 고려했을 때 아래의 기술스텍을 사용하고자 한다.
- Turborepo: 모노레포의 빌드 캐싱과 효율적인 워크스페이스 관리.
- Vite + Rollup: 개발 서버의 빠른 피드백(Vite)과 라이브러리 배포를 위한 최적화된 번들링(Rollup).
- pnpm: 엄격하고 빠른 패키지 관리.
- Storybook: 독립적인 컴포넌트 주도 개발(CDD) 환경 구축.
폴더 구조
dobby-design-system/
├── apps/
│ ├── web/ # Next.js 앱 (실제 서비스 혹은 테스트용 - SSR 검증)
│ └── storybook/ # Storybook (문서화 전용 앱)
├── packages/
│ ├── ui/ # UI 라이브러리 (Vite + Rollup + vanilla-extract)
│ ├── theme/ # 디자인 토큰 (공통 변수 관리)
│ ├── eslint-config/ # 공통 Eslint 설정
│ ├── typescript-config/ # 공통 TypeScript 설정
│ └── utils/ # 공통 유틸리티
├── turbo.json
└── package.json
마무리
물론 만들면서 구조적으로 조금씩 바뀔수도 있고, 결국 tailwind를 선택해서 다시 만들수도 있겠지만… 일단은 이런 생각으로 시작해볼려고 한다. 과연 언제쯤 사용가능할까…? 목표는 올해 상반기..!!