디자인 컴포넌트 라이브러리를 ‘실제 사용 방식’에 맞게 다시 설계한 이야기

디자인 컴포넌트 라이브러리 빌드 및 구조 개선기

들어가며

안녕하세요. 프로덕트웹개발2팀의 권가은입니다. 🙂

디자인 시스템은 한 번 만들고 끝나는 것이 아니라, 실제 서비스 안에서 사용되며 지속적으로 변화해야 합니다.
시간이 지나면, 이런 발전 과정에서 설계 당시의 구조를 그대로 유지하기가 점점 어려워집니다.
사용 방식이 달라질수록 기존 구조는 현재의 요구를 온전히 담아내지 못하고, 결국 변화의 속도를 따라가지 못하는 시점이 찾아옵니다.

이 글은 디자인 컴포넌트 라이브러리를 실제 사용 방식의 관점에서 다시 바라보며 진행한 구조 개선 과정을 정리한 글입니다. 디자인 시스템이나 공통 라이브러리를 운영하며 비슷한 고민을 해보신 분들께 작은 참고가 될 수 있기를 바랍니다.


kitchen 스토리북 1 kitchen 스토리북 2
© 2025. Kurly. All rights reserved.


KLDS(Kurly Logistics Design System)는 풀필먼트 조직 내에서 사용하는 디자인 시스템입니다.
몇 년 전, 이 디자인 시스템을 웹 환경에서 활용할 수 있도록 Kitchen이라는 React 기반의 디자인 컴포넌트 라이브러리를 만들었습니다.
Kitchen이라는 이름은 디자인 컴포넌트들이 조합되어 하나의 서비스 화면을 만든다는 점에서, 여러 재료를 다루는 '주방(Kitchen)'의 역할에 빗대어 지어진 이름입니다.

이후 Kitchen 라이브러리는 여러 서비스에 도입되며 다양한 추가 요구사항을 맞이하게 되었고, 그에 따라 라이브러리의 규모도 점차 커지기 시작했습니다.
그 과정에서 기존의 빌드 및 번들링 방식이 실제 사용 환경에서 불편함으로 드러나기 시작했습니다.

이 글에서는 특히 트리셰이킹이 적용되지 않던 기존 구조가 어떤 제약을 만들고 있었는지,
그리고 이를 어떤 기준으로 재정리하며 해소해 나갔는지를 중심으로 다루겠습니다.


🧩 무엇이 문제였을까

Kitchen에서 드러난 문제는 단순한 성능 이슈라기보다, 빌드와 번들 구조로 인해 설계 의도와 실제 사용 결과가 어긋나 있던 상태였습니다.
이로 인해 컴포넌트 설계와 확장 결정 전반에 지속적인 제약이 이어지고 있었습니다.

핵심적인 문제는 다음과 같았습니다.

  • 트리셰이킹이 불가능한 빌드 구조

    → 일부 컴포넌트만 사용해도 Kitchen 전체 코드가 번들에 포함됨

  • 컴포넌트 단위 패키지를 유지하지만, 실제로는 단일 진입점으로만 소비되는 구조

  • 빌드 산출물 기준으로 컴포넌트 간 참조가 이루어지는 구조


🌱 Kitchen은 어떻게 시작되었을까

이러한 문제들은 처음부터 잘못된 선택의 결과는 아니었습니다.
Kitchen이 처음 구축되었을 당시에는, 당시의 기술 환경과 사용 맥락을 고려한 분명한 설계 의도가 있었습니다.

초기 Kitchen의 목표는 다음과 같았습니다.

  • 최신 웹 환경이 아닌 서비스에서도 디자인 시스템을 사용할 수 있도록 지원할 것
  • 컴포넌트 단위로 독립적인 배포와 버전 관리를 가능하게 할 것

이를 위해 Kitchen은

  • UMD 방식으로 빌드되어 다양한 실행 환경에서 동작할 수 있도록 했고
  • 컴포넌트별 패키지 구조를 통해 각 컴포넌트를 독립적으로 관리할 수 있도록 설계되었습니다.

이 구조는 당시로서는 합리적인 선택이었고, 여러 환경에서 디자인 시스템을 재사용할 수 있게 되었습니다.


🔄 상황은 어떻게 달라졌을까

시간이 지나면서 Kitchen을 사용하는 환경과 방식에도 변화가 생겼습니다.
풀필먼트 조직에서 레거시 웹 환경이 점차 정리되었고, Kitchen 역시 최신 웹 환경을 기준으로 사용되는 경우가 늘어났습니다.
이와 함께 각 서비스에서는 Kitchen 전체가 아니라, 필요한 컴포넌트 몇 가지만 선택적으로 사용하는 사례가 점점 많아졌습니다.
이 과정에서 일부 컴포넌트만 import해도 실제로는 Kitchen 전체 코드가 번들에 포함된다는 문제가 분명하게 드러났습니다.
또한 컴포넌트별 패키지 구조는 배포 인프라와 템플릿 복잡도를 키우며, 개발 과정에서 의존 관계를 관리하기 어려운 구조로 이어졌습니다.

즉, 초기에는 장점이었던 구조가 사용 방식이 달라진 이후에는 제약으로 작용하기 시작한 상황이었습니다.

1) 트리셰이킹이 적용되지 않는 구조

기존 kitchen 구조
© 2025. Kurly. All rights reserved.

Kitchen은 컴포넌트 단위로 패키지가 나뉘어 있었지만,
실제 배포 시에는 kitchen-core에서 모든 컴포넌트를 re-export하는 구조를 가지고 있었습니다.

또한 초기에는 다양한 실행 환경을 지원하고, 각 컴포넌트를 독립적으로 관리할 수 있도록 하기 위해
개별 컴포넌트는 UMD, kitchen-core는 CommonJS로 빌드되고 있었습니다.
때문에 번들러가 모듈 간 사용 관계를 정적으로 분석하기 어려운 상태였습니다.

그 결과, 사용 여부와 관계없이 컴포넌트 코드가 번들에 포함되었고
Kitchen의 규모가 커질수록 번들 사이즈 역시 함께 증가했습니다.

즉, 디자인 시스템을 사용하는 비용이 컴포넌트 단위로 분리되지 않는 구조였습니다.

2) 빌드 산출물 기준 참조로 인한 개발 환경 복잡도

트리셰이킹이 적용되지 않는 구조에서는
여러 컴포넌트 패키지가 서로를 상대 경로 기준으로 참조할 경우 동일한 코드가 패키지마다 중복 빌드되는 문제가 발생할 수 있었습니다.
이를 피하기 위해 컴포넌트 내부에서는 다른 Kitchen 컴포넌트를 상대 경로가 아닌 빌드 산출물 기준으로 참조하는 방식을 선택하게 되었습니다.
이 선택은 단기적으로는 빌드 중복을 줄이는 데 도움이 되었지만, 개발 환경 전반에는 새로운 제약으로 작용했습니다.

  • Storybook에서는 컴포넌트를 직접 참조하지 않기 때문에 propTypes 기반의 자동 추론이 동작하지 않았고, 이를 보완하기 위한 별도의 우회 코드가 필요했습니다.
스토리북 우회 코드 예시 1 스토리북 우회 코드 예시 2
© 2025. Kurly. All rights reserved.
  • 로컬 환경에서는 의존성이 있는 컴포넌트를 단독으로 실행하거나 수정 결과를 확인하기 어려웠으며
  • 테스트 환경에서도 하위 컴포넌트를 직접 다루기 어려워 모킹 코드가 점점 복잡해졌습니다.

기존 구조를 유지한 채 확장을 이어가면서,
빌드 구조의 제약은 개발 경험(DX) 전반의 저하로 확산되고 있었습니다.

3) 구조적 제약이 설계 선택에 영향을 미치는 상황

이러한 제약은 단순한 불편함을 넘어, 설계 의사결정에도 영향을 주기 시작했습니다.

Table, Chart처럼 상대적으로 무거운 컴포넌트를 새롭게 구축하려해도
컴포넌트의 책임이나 API 설계보다 “번들에 어떤 영향을 미칠지”가 먼저 고려되는 상황이 반복되었습니다.

그 결과 Kitchen은 기능적으로는 정상 동작했지만,
구조적인 이유로 확장과 실험을 망설이게 만드는 라이브러리로 변해가고 있었습니다.


🔍 원인은 어디에 있었을까

문제의 핵심은 구조에 있었습니다.

kitchen-core의 re-export 중심 진입점 위에 UMD와 CJS가 혼합된 빌드 방식이 더해지면서, 번들러가 모듈 간 사용 관계를 정적으로 추적하기 어려운 형태가 되었습니다.

그 결과 트리셰이킹은 기술적인 문제가 아니라, 구조 자체가 만들어낸 한계에 가까웠습니다.


🧭 구조를 다시 설계하며 세운 기준

구조 개선 과정에서는 단순히 코드를 정리하는 것을 넘어
패키지 경계, export 방식, 빌드/번들링 전략, 그리고 공통 자산(foundation)의 책임 범위와 같은 선택을 계속해서 해야 했습니다.

그 과정에서 “어떤 선택이 더 적합한가”를 판단하기 위한 기준이 필요했고,
아래 항목들은 구조 개선 전반에서 의사결정을 내릴 때 공통적으로 참고했던 판단 기준입니다.

  1. 실제 사용 방식과 구조가 최대한 일치해야 한다.
  2. 번들러가 정적으로 분석할 수 있는 구조여야 한다.
  3. 단기 성능보다 중장기 유지 보수성을 우선한다.

이 과정에서 Kitchen 자체의 구조뿐 아니라,
디자인 토큰과 같은 공통 자산을 어디에서, 어떤 책임으로 관리할 것인지에 대한 고민도 자연스럽게 이어졌습니다.


🛠️ 우리는 이렇게 바꿨다

1️⃣ 빌드·번들 구조 개선 — ESM 전환과 exports 명시

→ 트리셰이킹이 동작하는 조건 만들기

기존 Kitchen은 여러 패키지와 re-export 구조를 통해 제공되고 있었고, 이로 인해 번들러가 실제 사용 여부와 관계없이 전체 코드를 포함하는 문제가 있었습니다.

이를 개선하기 위해

  • 배포 포맷을 ESM 기반(dist/esm)으로 전환하고,
  • exports 필드를 통해 외부에 노출되는 모듈 범위를 명시적으로 정의했습니다.

이 과정에서 중요한 기준은 번들러가 추가적인 설정 없이도 import 구문만으로 의존성을 추적할 수 있는가였습니다.
CommonJS 기반 구조를 유지한 채 번들러 옵션으로 보완하는 방식도 고려했지만
이 경우 사용 환경이나 빌드 도구에 따라 트리셰이킹 결과가 달라질 수 있었고, 라이브러리를 사용하는 쪽에서 구조보다 설정을 더 많이 의식해야 하는 상황이 될 수 있다고 판단했습니다.

또한 현재 Kitchen을 사용하는 서비스들은 모두 웹 번들러 기반의 React 환경이어서, Node 환경이나 UMD / CommonJS 포맷이 필요한 사용처가 존재하지 않았습니다. 따라서 여러 배포 포맷을 동시에 지원해야 할 필요성이 크지 않았습니다.

이러한 맥락에서 배포 포맷을 ESM-only로 명확히 가져가고, 구조 자체를 단순화해 import 구문만으로도 의존성을 예측할 수 있는 형태를 선택했습니다.

이제 라이브러리를 사용하는 입장에서 실제로 import된 컴포넌트만을 기준으로 번들을 구성할 수 있게 되었습니다.

2️⃣ 패키지 구조 단순화 — 멀티 패키지에서 단일 패키지로

→ 개발 단위와 배포 단위를 일치시킨다

Before                                  After

packages/kitchen-core                   packages/kitchen
├ 전체 컴포넌트 re-export (진입점)           └ src/components/...
...                                     packages/kitchen-foundation
packages/kitchen-button
packages/kitchen-text
packages/kitchen-
...총 28개

기존에는 컴포넌트 단위 패키지를 유지하고 있었지만, 실제 사용은 단일 진입점에 집중되어 있었습니다.
그 결과 컴포넌트 간 참조 기준이 상대 경로가 아닌 빌드 산출물 기준으로 굳어졌습니다.

이 방식은 Storybook이나 테스트 환경에서 컴포넌트를 직접 다루기 어렵게 만들었고, 개발 러닝커브와 DX 전반의 저하로 이어졌습니다.
또한 각 패키지가 독립적인 버전이나 배포 단위로 관리되고 있지 않음에도 패키지별 설정 파일과 빌드/테스트 환경을 유지해야 했습니다. 그 과정에서 사용되지 않거나 deprecated된 코드들이 누적되었고, 구조를 유지하는 것 자체가 점점 더 큰 관리 비용으로 이어졌습니다.

결국 문제는 노출 방식이나 개별 패키지의 문제가 아니라, 실제 사용 단위와 구조가 어긋나 있다는 점이라고 판단했습니다.

이를 해소하기 위해 컴포넌트 단위 패키지를 제거하고, packages/kitchen 단일 패키지 구조로 전환했습니다.

단일 패키지 구조로 전환하면서 빌드 시간 증가나, 하나의 패키지가 담당하는 변경 범위가 넓어질 수 있다는 점에 대한 우려도 있었습니다.
빌드 시간의 경우 구조 변경 후 측정해본 결과, 현재 규모에서는 충분히 안정적인 수준임을 확인할 수 있었습니다. 변경 범위의 경우에도 기존에도 단일 배포 단위로 운영되고 있었기 때문에, 이 부분은 이후 운영 과정에서 계속 관찰해볼 항목으로 정리했습니다.

이제 컴포넌트 간 의존 관계를 빌드 결과물이 아닌 소스 코드 기준으로 명확하게 표현할 수 있게 되었습니다.

3️⃣ foundation 패키지 분리 — 공통 자산 관리의 기준 정리

→ 디자인 토큰을 단일 책임으로 관리하기 위한 구조 정비

     [kitchen-foundation]
          ↓       ↓
 [Desktop DS]   [Mobile DS]

기존 KLDS 구조에서는 Desktop(Web)과 Mobile 디자인 시스템이 서로 다른 레포지토리에서 운영되고 있었고, 컬러와 타이포그래피 같은 디자인 토큰 역시 각각 관리되고 있었습니다.

이로 인해

  • 토큰 변경 시 양쪽에 동일한 작업이 반복되었고,
  • 작업 시점이 어긋날 경우 변경 사항이 누락될 가능성도 존재했습니다.

문제의 핵심은 기술적 의존성보다는 공통 자산을 관리하는 기준이 분산되어 있다는 점이었습니다.

이를 해결하기 위해 공통 자산을 kitchen-foundation 패키지로 분리하고,
Desktop(Kitchen)과 향후 Mobile이 동일한 foundation을 의존하도록 구조를 정리했습니다.

번들러 변경에 대한 고민

구조 개선 과정에서 Rollup과 같은 번들러 전환도 고려했지만, 팀 내 빌드 환경 변경에 따른 영향 범위를 감안해 기존 도구를 유지한 채 구조와 배포 방식을 우선 정리했습니다.

향후 Mobile 환경과의 통합이나 라이브러리 빌드 요구사항 변화에 따라 번들러 전환 역시 다시 검토해볼 수 있는 선택지로 남겨두고 있습니다.


📈 그래서 얼마나 달라졌을까

구조 개선의 결과는 두 가지 관점에서 확인할 수 있었습니다.

Kitchen 자체의 변화 (DX)

항목 Before After 개선
빌드 시간 60.93초 6.94초 88.6% 단축 (8.8배)
개발 확인 루프 (Hot) 35.21초 HMR 즉시 반영  
개발 확인 루프 (Cold) 86.34초 20.87초 75.82% 단축 (4.14배)
코드량 변화     2만8천 줄 감소

동일 환경에서 각 3회 측정한 평균값입니다.
Hot은 Storybook 실행 상태에서 1줄 변경 후 반영까지,
Cold는 캐시 제거 후 Storybook 첫 렌더까지 측정했습니다.

코드를 수정하고 바로 확인할 수 있는 환경이 다시 가능해졌습니다.

Kitchen 사용부의 변화 (번들)

  • Kitchen 코드 포함량

    항목 Before After 개선
    공통 번들 내 Kitchen 코드 크기 (parsed size) 1.61MB 162.5KB 89.97% 감소

    위 수치는 Kitchen 라이브러리 자체 크기가 아니라,
    측정 서비스(Webpack 설정 포함)에서 실제로 포함된 Kitchen 코드량을 의미합니다


  • 트리셰이킹 적용 확인: 컴포넌트 전체 사용 페이지 vs 최소 사용 페이지 번들 분석
컴포넌트 전체 사용
전체 사용 페이지 bundle 분석 결과 (136KB / parsed size)
컴포넌트 최소 사용
최소 사용 페이지 bundle 분석 결과 (1.73KB / parsed size)
© 2025. Kurly. All rights reserved.


이제 컴포넌트를 추가하는 일이 더 이상 성능 리스크로 먼저 고려되지 않게 되었습니다.


✨ 마치며

이번 Kitchen 구조 개선을 통해 디자인 시스템을 “어떻게 만들 것인가”보다 “어떻게 계속 사용될 것인가”를 먼저 고민하는 것이 중요하다는 점을 다시 한번 느꼈습니다. 구조를 정리하는 과정에서 성능 지표나 빌드 결과만큼이나, 실제 사용하는 개발자 입장에서의 흐름과 제약이 설계에 얼마나 큰 영향을 미치는지도 분명해졌습니다.
이번 정리는 Kitchen(Web)을 중심으로 한 이야기이지만, KLDS 전체를 장기적으로 운영하기 위한 출발점이기도 합니다. 앞으로는 이 구조를 기반으로 릴리즈 자동화나 디자인 토큰 확장 등 다음 단계의 개선을 차근차근 이어가고자 합니다.
이 글이 디자인 시스템을 운영하거나 구조를 고민하고 계신 분들께 작은 참고 사례가 되었으면 좋겠습니다.
글 읽어주셔서 감사합니다.