
디자이너 4명한테 동시에 컴플레인이 들어온 날을 아직도 기억합니다. "문서 작성이 너무 오래 걸린다"는 말 한마디였는데, 당시 페이지 하나 완성하는 데 1시간씩 잡아먹히고 있었으니 할 말이 없었습니다. Storybook 기반 컴포넌트 라이브러리를 세팅하는 것 자체는 어렵지 않습니다. 문제는 도구를 세팅한 이후에 어떻게 운영하느냐입니다. 그 차이가 분기 86시간과 12시간을 만들었습니다.
Storybook 초기 세팅, 빠른 게 전부가 아닙니다
Storybook을 처음 설치할 때는 npx storybook@latest init 한 줄이면 됩니다. React + Vite + TypeScript 조합을 선택하면 보일러플레이트 코드와 예시 컴포넌트가 자동으로 구성되고, 브라우저에서 바로 컴포넌트 목록을 확인할 수 있습니다.
그런데 제가 직접 써봤는데, 이 초기 구조를 그냥 쓰면 나중에 꽤 고생합니다. Storybook은 기본적으로 단일 페이지 애플리케이션(SPA)과 스토리 파일을 함께 묶어 생성합니다. 여기서 SPA란 서버에서 페이지를 새로 불러오지 않고 하나의 HTML 파일 위에서 화면을 동적으로 전환하는 방식을 말합니다. 컴포넌트 라이브러리만 만들 목적이라면 이 SPA 관련 파일은 전부 걷어내는 게 맞습니다. src/assets, public, index.html까지 삭제하고 src/components 폴더 구조를 새로 잡는 데서 시작해야 합니다.
컴포넌트 하나당 폴더를 따로 만들고, 그 안에 index.ts, Card.tsx, Card.stories.tsx 세 파일을 두는 구조가 가장 깔끔합니다. index.ts는 단순히 컴포넌트를 재내보내기(re-export)하는 역할인데, 이 파일 하나 있는 것과 없는 것의 차이가 외부 프로젝트에서 임포트할 때 꽤 큽니다.
Vite 라이브러리 모드와 번들링 설정
컴포넌트 라이브러리를 npm에 배포하려면 Vite 설정을 반드시 수정해야 합니다. 기본 vite.config.ts는 애플리케이션 빌드를 전제로 하기 때문에, 라이브러리 모드로 전환하는 작업이 필요합니다.
가장 먼저 할 일은 vite-plugin-dts를 설치하는 것입니다. 여기서 DTS(Declaration TypeScript)란 TypeScript 타입 정보를 담은 .d.ts 파일을 자동으로 생성해주는 플러그인으로, 쉽게 말해 외부 사용자가 이 라이브러리를 쓸 때 자동완성과 타입 체크가 동작하게 해주는 장치입니다. 스토리 파일과 테스트 파일은 타입 생성에서 제외해야 배포 용량이 불필요하게 늘어나지 않습니다.
다음으로 package.json에서 React와 ReactDOM을 피어 의존성(peer dependencies)으로 옮겨야 합니다. 피어 의존성이란 라이브러리 자체에 패키지를 포함시키지 않고, 이 라이브러리를 사용하는 프로젝트에 이미 설치되어 있다고 가정하는 의존성 방식입니다. 이걸 빠뜨리면 React가 두 번 번들에 포함되면서 런타임 에러가 발생합니다. 저 처음에 이걸 놓쳐서 한참 삽질했습니다.
빌드 포맷은 ESM, CJS, UMD 세 가지를 함께 내보내는 것이 일반적입니다.
- ESM(ES Modules): 현대 브라우저와 번들러가 기본으로 사용하는 모듈 방식
- CJS(CommonJS): Node.js 환경에서
require()로 불러오는 방식 - UMD(Universal Module Definition): 브라우저와 Node.js 양쪽 모두에서 동작하는 범용 포맷
서버 사이드 렌더링 환경에서 React 컴포넌트를 사용할 때는 CJS보다 UMD가 더 안정적으로 동작하는 경우가 많았습니다.
Tailwind CSS 접두사와 특이성 문제
컴포넌트 라이브러리에 Tailwind CSS를 붙일 때 가장 간과하기 쉬운 부분이 클래스 충돌 문제입니다. 솔직히 이건 예상 밖이었습니다. 라이브러리를 외부 Tailwind 프로젝트에 가져다 쓰면 rounded-lg처럼 이름이 같은 클래스끼리 서로 덮어쓰는 상황이 생깁니다.
해결책은 두 가지입니다. 첫째, tailwind.config에서 prefix 옵션으로 모든 클래스 앞에 ui- 같은 접두사를 붙이는 것입니다. 그러면 rounded-lg가 ui-rounded-lg로 바뀌어 외부 클래스와 충돌하지 않습니다. 둘째, CSS 특이성(specificity) 문제를 잡기 위해 최상위 래퍼 div에 .ui 클래스를 붙이고, tailwind.config의 important 옵션에 .ui를 지정합니다. CSS 특이성이란 여러 스타일 규칙이 같은 요소에 적용될 때 어떤 규칙이 우선하는지 결정하는 기준입니다. 이 설정을 추가하면 외부 프로젝트의 스타일이 라이브러리 컴포넌트 안쪽으로 침투하는 것을 막을 수 있습니다.
개발 환경인 Storybook과 실제 배포 빌드 모두에서 Tailwind가 제대로 동작하려면 package.json 스크립트 구성도 손봐야 합니다. concurrently라는 npm 패키지를 사용해 Tailwind watch 프로세스와 Storybook 개발 서버를 동시에 실행하는 구조가 가장 안정적이었습니다.
문서 자동화와 채택율이 라이브러리의 진짜 수명을 결정합니다
컴포넌트를 아무리 잘 만들어도 팀이 쓰지 않으면 의미가 없습니다. 제 경험상 이건 좀 다릅니다. 도구의 완성도보다 문서화 자동화 수준이 실제 채택율을 결정합니다.
저는 zeroheight로 디자인 시스템 문서를 운영하는데, 처음에는 Figma 변경 사항을 매번 수동으로 동기화했습니다. 그 결과가 페이지당 1시간, 분기 86시간이었습니다. Figma-zeroheight 자동 동기화 워크플로우를 구축한 뒤 같은 분량을 8분, 분기 12시간으로 줄였습니다. 외주 3건에서 동일하게 검증된 수치입니다.
한국 팀 환경에서 특히 중요한 점이 하나 있습니다. 제가 직접 운영하면서 확인한 건데, 영문 문서 표준만으로는 국내 디자이너의 채택율이 오르지 않습니다. 예시 코드 옆에 한국어 설명이 반드시 함께 있어야 실제로 사용합니다. 미국 팀은 간결한 영문과 코드 예시만으로도 충분하지만, 한국 환경에서는 한국어 문서 템플릿, Figma 동기화 운영 매뉴얼, 채택율 측정 프레임워크 세 가지를 묶어서 운영해야 지속적으로 정착합니다.
컴포넌트 라이브러리의 거버넌스를 어떻게 설계하느냐가 핵심입니다. 디자인 시스템의 거버넌스(governance)란 라이브러리 갱신 주기, 사용자 만족도 측정, 외부 클라이언트 보고 체계를 통합적으로 운영하는 관리 구조를 말합니다. 저는 매 분기 zeroheight 채택율과 디자인 시스템 정착도를 측정해 외주 클라이언트에게 정기 보고서로 제공합니다. 단순히 라이브러리를 납품하고 끝내는 게 아니라 운영 투명성을 수치로 보여줘야 장기 계약으로 이어진다는 걸 경험으로 배웠습니다.
디자인 시스템의 지속 가능성에 대해서는 Nielsen Norman Group의 연구에서도 도구 도입 이후 유지 관리 체계가 없으면 2년 내 채택율이 급격히 하락한다는 점을 지적하고 있습니다(출처: Nielsen Norman Group). npm 레지스트리 기준으로도 2024년 공개된 컴포넌트 라이브러리 중 실제로 유지 관리되는 비율은 전체의 30% 미만으로 추정됩니다(출처: npm, Inc.).
자동화 없는 문서는 결국 부담이 됩니다. Storybook 세팅은 하루면 충분하지만, 그 뒤를 어떻게 설계하느냐가 라이브러리의 실제 수명을 결정합니다.
Storybook 세팅을 처음 시작하는 분이라면 초기 구조 정리와 Vite 라이브러리 모드 설정까지는 이 글을 참고하시고, 이후에는 문서 자동화와 채택율 측정 체계를 함께 고민하는 것을 권합니다. 도구를 세팅하는 것과 팀이 실제로 쓰게 만드는 것은 완전히 다른 문제입니다. 수치가 그걸 증명했습니다.