Replies: 1 comment
-
개발 무지랭이여서 기술적인 내용까진 이해하지 못했지만, 합숙 때 들리던 바닐라익스트랙, panda css가 어떤 꼼꼼한 논리 과정을 거쳐 선택됐는지 이해할 수 있었어요 ! 글을 엄청 잘 쓰시네요 bb |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
저희 BOFIT 팀은 기술 스택을 선정할 때, 유명하거나 유행하는 기술이 아니라 우리 서비스에 가장 fit 한 선택이 무엇인지에 집중하고자 했습니다.
라는 막연한 선택이 아니라, 우리 팀의 현재 역량과 앞으로의 러닝커브와, WEBJAM 이라는 독특한 환경 등을 고려하여 실제로 우리가 만드는 서비스에 가장 잘 맞는 기술이 무엇인지 근거를 가지고 따져보았습니다.
기술 스택 선정은 늘 여러 선택지가 공존하고, 답이 존재하는
문제
가 아닙니다. 단지 이 글에선 BOFIT 팀이 어떤 기준을 세웠고, 수많은 선택지 중에서 왜 그 길을 택했는지 그 과정을 하나씩 풀어보고자 합니다.🚀 모노레포(터보레포) : 디자인 시스템의 존재 이유와 Headless UI
출처: Turborepo 공식홈페이지
BOFIT 팀은 이번 프로젝트를 설계하면서 단순히 클라이언트 레포 하나에 컴포넌트들을 모아두는 방식이 아니라,
디자인 시스템을 별도의 패키지로 분리하고 모노레포 구조로 관리하기로 결정했습니다.
왜 굳이 분리할까요?
좋은 컴포넌트를 만드는 것만으로는 훌륭한 UI를 만들 수 없다고 생각했기 때문입니다.
우리는 기능을 먼저 만들고 그 위에 UI를 덧붙이는 방식이 아니라,
UI의 구조와 일관성을 먼저 설계하고 그 위에 기능을 얹는 방식으로 접근하고자 했습니다.
디자인 시스템을 하나의 독립된 패키지로 관리하면,
Headless UI 패턴과의 연결고리
우리가 지향하는 디자인 시스템은 Headless UI 패턴과도 닮아있습니다.
예를 들어 캘린더를 만든다고 할 때,
이렇게 하면 관심사 분리(Separation of Concerns) 가 이루어집니다.
덕분에:
즉, Headless UI는 우리의 디자인 시스템이 확장성과 유지보수성을 동시에 확보할 수 있는 강력한 무기입니다.
모노레포 + 터보레포의 역할
BOFIT 팀은 TurboRepo를 활용해 모노레포 환경에서
bds-ui
,bds-icon
)여러 패키지를 하나의 저장소에서 버전과 빌드 흐름을 관리합니다.
이렇게 하면:
오버 엔지니어링? 하지만 더 중요한 목표
물론 아직은 프로젝트를 대규모로 확장할 계획이 없는데,
“이게 혹시 오버 엔지니어링 아닌가?”라는 의문이 있을 수도 있습니다.
하지만 우리는 이 구조를 단지 지금의 효율만을 위해 선택한 것이 아닙니다.
무엇보다 팀원 모두가 컴포넌트 설계와 구현을 디자인 시스템 중심 사고로 접근해 보는 경험을 쌓는 것이 중요하다고 판단했습니다.
이 경험은 웹잼 기간 동안에 그치지 않고,
팀원들이 앞으로 다른 프로젝트를 진행하거나 더 큰 규모의 협업을 할 때에도
역할 분리와 UI 일관성 유지라는 기본 원칙을 자연스럽게 체득할 수 있는 소중한 학습 기회가 될 것이라 믿습니다.
또한 BOFIT 팀은 향후 프로젝트가 확장될 가능성이 있다고 판단했습니다.
처음부터 모노레포로 세팅해 두면, 새로운 서비스나 패키지가 추가될 때에도
구조적 부담 없이 효율적으로 관리할 수 있습니다.
만약 디자인 시스템을 별도의 레포로 분리해 두면,
장기적으로는 독립성과 배포 측면에서 장점이 있겠지만,
웹잼이라는 짧은 기간 동안에는 여러 레포를 오가며 관리해야 하는 불편함과 오버헤드가 더 클 수 있다고 판단했습니다.
그래서 현재는 하나의 모노레포로 모든 흐름을 집중 관리해 개발 효율성을 극대화하기로 했습니다.
🍦CSS 라이브러리
저희는 회의 끝에 Runtime 스타일링을 배제하기로 결정했습니다. 물론 buldtime 과 runtime 스타일링의 장단점을 분석한 이후에요.
CSS-in-JS 방식은 빠르게 개발하고, 유연하게 커스터마이징 할 수 있다는 장점이 있어요. 하지만 디자인 시스템이 존재하는
BOFIT
프로젝트에서는 단점이 더 크게 느껴졌어요.그래서 후보는? : Vanila-extract , tailwind V4 , Panda CSS
빌드 타임 스타일링 라이브러리중에서 두 가지 기준을 가지고 비교하며 검토했어요
가독성
이었어요.그래서 처음에는 Tailwind CSS 도 후보군에 올려두고 고민했습니다.
Tailwind는 독자적인 CSS 문법(
mt-10
과 같은 유틸리티 클래스)을 사용하여 생산성을 극대화할 수 있다는 점이 가장 큰 장점입니다.하지만 우리 팀원 모두가 Tailwind를 실제로 사용해본 경험이 없었고, 웹잼이라는 장기 해커톤 환경에서는 새 문법에 적응하는 학습 비용이 오히려 단점이 될 수 있다고 판단했어요.
무엇보다 팀원 모두가 인라인 형태의 Tailwind 문법을 선호하지 않았고, 스타일 파일과 TSX 파일을 분리해 관리하는 방식을 더 가치 있게 봤습니다.
Tailwind의 최대 강점인 생산성을 저희 팀 상황에서는 100% 발휘하기 어렵다고 판단했고, 결국 가독성과 유지보수성을 위해 제외하기로 결정했습니다.
🐼 Panda CSS
그 다음 후보는 Panda CSS였어요.
Panda CSS는 Tailwind처럼 유틸리티 기반 스타일링을 제공하면서도, TypeScript 기반으로 동작하여 디자인 토큰 관리에 강점이 있다는 점에서 후보군에 올랐습니다.
실제로 저희는 Panda CSS를 직접 적용해보고자 초기 세팅까지 진행했어요.
다만 많은 팀원들이 Panda CSS의 초기 세팅이 어렵다는 피드백을 주었습니다.
하지만 개인적인 체감으로는 세팅 자체가 복잡하다기보다는 레퍼런스 자료가 부족한 것이 원인이라고 생각했습니다. 즉, 커뮤니티가 아직 활성화 되지 않은 것이죠.
그렇게 Panda CSS에서 디자인 토큰 정의를 마치고 빌드를 실행했는데,
styled-system
덕분에 Tailwind와 유사한 유틸리티 클래스가 굉장히 많이 생성되더라고요.하지만 저희 팀은 처음부터 유틸리티 클래스 사용을 배제하기로 했고, 특히 인라인 스타일링에 대한 거부감이 강했어요.
그래서
config
파일에서 유틸리티 클래스 생성을 막는 옵션을 추가한 뒤 다시 빌드를 진행했습니다.그러나 이렇게 Panda CSS를 사용한다면, Panda CSS의 핵심 장점인 유틸리티 기반 스타일링을 제대로 활용하지 못한다는 결론에 도달했습니다.
결국 Panda CSS는 저희 팀의 스타일링 방향성과는 맞지 않는다고 판단했습니다.
🍦 Vanilla Extract
저희는 다양한 스타일링 라이브러리를 비교하고, 실제 디자인 시스템 요구사항에 맞춰 실험한 끝에 Vanilla Extract를 최종 선택했습니다.
선택 이유
유틸리티와 타입 안전성
Vanilla Extract는
styleVariants
,recipes
,sprinkles
,createThemeContract
등 디자인 시스템 구축에 필요한 강력한 유틸리티를 제공합니다.또한 TypeScript와의 긴밀한 통합 덕분에 디자인 토큰과 테마를 타입 안전하게 관리할 수 있어, 확장성이 높고 실수를 줄일 수 있었습니다.
CSS 변수 기반 디자인 토큰 관리
예를 들어 Emotion 기반 런타임 테마는 테마 전환 시 리렌더링과 복잡한 상태 동기화가 필요했고, 깜빡임(FOUC) 문제가 있었습니다.
Vanilla Extract에서는
createGlobalThemeContract
와createGlobalTheme
API를 통해 CSS 변수 기반으로 토큰을 선언하고 브라우저 레벨에서 관리하여,런타임 오버헤드 없이도 일관된 디자인 토큰 관리가 가능해졌습니다.
정적 빌드 + 제로 런타임
스타일을 런타임이 아니라 빌드 시점에 추출하기 때문에 불필요한 번들 크기나 성능 부담이 없습니다.
필요하다면 가벼운 런타임 스타일도 지원하므로, 상황에 따라 유연한 적용이 가능합니다.
프레임워크 무관 + 직관적 구조
Vanilla Extract는 프레임워크에 종속되지 않기 때문에, Next.js / Vite 등 다양한 환경에서 일관되게 사용 가능합니다.
또한 CSS 파일을 직접 다루듯 작성할 수 있어 기존 CSS/SCSS 문법과 크게 다르지 않아 팀 전체가 빠르게 적응할 수 있었습니다.
디자인 시스템과의 높은 시너지
CSS 변수로 테마를 관리하면서, 디자인 토큰의 가독성과 유지보수성을 크게 높였습니다.
개발자 도구에서 변수명을 바로 확인할 수 있어 디자이너와 원활하게 협업할 수 있었고, 오타나 잘못된 참조를 사전에 방지할 수 있었습니다.
결과적으로, Vanilla Extract는 타입 안전성과 디자인 토큰 관리, 제로 런타임의 장점을 동시에 제공하며,
디자인 시스템을 지속적으로 고도화할 수 있는 가장 적합한 선택지였습니다.
📕 Storybook
출처: Storybook 공식 홈페이지
좋은 디자인 시스템은 좋은 컴포넌트를 만드는 것에 그치면 안된다고 생각합니다. 이 컴포넌트를 어떻게 보여주고, 테스트하고, 협업하는 팀원들과 함께 소통할지가 중요한 포인트죠.
BOFIT 팀은 스토리북을 다음과 같은 이유로 도입하였습니다.
Addon 활용으로 확장된 Storybook
저희는 기본 Storybook 환경에 다음과 같은 Addon을 적극 활용하고 있습니다.
@storybook/addon-essentials
: Storybook을 구성하는 데 필수적인 기능 모음입니다.Controls, Docs, Actions, Viewport 등 컴포넌트의 상태를 실시간으로 조작하거나, 문서를 자동으로 생성하는 기능을 통해 개발자와 디자이너가 동일한 화면에서 피드백하고 소통할 수 있도록 돕습니다.
@storybook/addon-a11y
: 접근성 검사 도구를 스토리북에 통합합니다.이 Addon을 통해 각 컴포넌트가 WCAG 가이드라인에 맞게 충분히 접근성을 고려하고 있는지 실시간으로 점검하며 개발합니다.
개발 단계부터 접근성을 기본값으로 가져가려는 저희 팀의 목표와 잘 맞아떨어집니다.
@storybook/addon-onboarding
: 스토리북을 처음 사용하는 팀원도 쉽게 이해하고, 사용 흐름을 빠르게 익힐 수 있도록 가이드를 제공합니다.특히 새로운 팀원이 합류하거나, 디자인 시스템을 확장할 때에도 학습 장벽을 낮출 수 있었습니다.
무엇보다 저희는 단순한 개발에 그치지 않고, 웹잼(Web Jam) 기간 동안에도 문서화를 체계적으로 관리하고 공유하는 것을 목표로 하고 있습니다.
Storybook은 이러한 목적의식과 잘 맞아떨어지는 협업 도구로써, 개발-디자인 간 경계를 허물고 더 나은 디자인 시스템 품질을 유지하는 데 중요한 역할을 합니다.
⚙️ 상태 관리 라이브러리
출처: Zustand 공식 홈페이지
BOFIT 팀은 다양한 선택지(Redux, Recoil, Zustand 등)를 충분히 검토했지만, 최종적으로는 별도의 전역 상태 관리 라이브러리를 사용하지 않기로 결정했습니다.
이유는 명확했습니다.
과거에는 Redux 같은 라이브러리가 없으면 복잡한 상태 흐름을 관리하기 어렵던 시절이 있었지만, 지금은 상황이 다릅니다.
오히려 불필요한 전역 상태는 복잡성을 만들고, 흐름을 추적하기 어렵게 하며 디버깅 비용을 높입니다.
저희는 실제 서비스에서 전역으로 공유해야 할 상태가 거의 없다고 판단했고, 필요하다면 컴포넌트 구조를 리팩토링해 Context만으로도 충분히 해결할 수 있다고 결론지었습니다.
따라서 BOFIT 팀은 전역 상태 관리 도구 대신 React의 기본 기능과 Context, React Query를 중심으로 상태를 관리하고,
필요 이상으로 복잡성을 키우지 않는 가벼운 설계를 선택했습니다.
📁 폴더구조 : 기능 중심 사고를 담은 구조
프로젝트의 폴더 구조를 설계하기 앞서, 우리 팀이 추구하는 목표와 방향성을 명확히 하는 것이 중요했습니다.
우리의 목표 설정
단기 목표: 기능 구현 우선
웹잼 기간 동안 BOFIT 팀의 핵심 목표는 **기능 구현(Function Implement)**입니다.
팀원 모두가 명확한 기능 목표를 달성하며, 사용자에게 실제로 동작하는 결과물을 제공하는 것이 최우선입니다.
물론 Clean Code를 함께 추구하는 것이 이상적이지만, 현실적으로 개발 초기 단계에서부터 완벽한 아키텍처를 설계하는 것은 쉽지 않습니다.
그래서 단기적으로는 기능 구현에 집중하고, 장기적으로는 코드 품질과 아키텍처 정교화로 이어지는 방향을 설정했습니다.
장기 목표: Clean Code 지향
장기적으로는 **확장성(Extensibility), 재사용성(Reusability), 유지보수성(Maintainability), 가독성(Readability)**을 갖춘 Clean Code가 핵심 목표입니다.
관심사 분리: 인지 부하를 줄이는 핵심 원칙
관심사 분리는 개발자 입장에서 인지 부하(Cognitive Load)를 줄이는 데 매우 효과적입니다.
시각적 비유로 이해하기
복잡한 그림에서 특정 도형을 찾는 상황을 생각해보세요.
코드도 마찬가지입니다.
여러 관심사가 한 파일에 혼재되어 있으면 조건과 맥락이 뒤섞여 혼란이 극대화됩니다.
따라서 하나의 명확한 역할만 수행하는 단위로 코드를 나누어야 합니다.
프론트엔드에서의 관심사 분리 발전사
웹 프론트엔드는 그 자체로 관심사 분리를 지향하는 발전의 역사였습니다.
수평적 계층 구조: HTML, CSS, JavaScript, API 등 여러 계층으로 나누어 관심사를 분리
컴포넌트의 등장: 수평적 계층 구조를 수직으로 관통하는 새로운 분류 방식
HTML-CSS 의존성 해결: CSS Modules, CSS-in-JS 등을 통한 단방향 의존 구조
비즈니스 로직과 뷰 로직 분리:
서버 상태 관리: API 호출, 캐싱, 페이지네이션, 에러 처리 등을 별도 계층으로 분리
기존 폴더 구조의 한계
보편적인 "역할별 구조"의 문제점
현재 가장 보편적인 폴더 구조는 다음과 같습니다:
이 구조는 **역할별로 나뉜 구조(Role-Sliced Design)**로, 다음과 같은 장점이 있습니다:
하지만 규모가 커지면서 드러나는 한계
프로젝트 규모가 커지면 이 구조의 한계가 드러납니다:
예를 들어
cart
도메인을 완전히 제거해야 한다면:/components
에서 cart 관련 컴포넌트들/hooks
에서 cart 관련 훅들/services
에서 cart 관련 서비스들/api
에서 cart 관련 API들모든 폴더를 뒤져서 관련 파일들을 찾아 제거해야 합니다. 이는 지우기 어려운 코드를 만듭니다.
FSD(Feature-Sliced Design) 구조의 도입
FSD란?
Feature-Sliced Design은 기능(Feature)을 기준으로 코드를 분리하는 아키텍처입니다.
기존에는 역할에 따라 파일과 폴더가 분리되었지만, 이제는 하나의 기능 단위로 모든 파일을 같은 폴더에 모아 관리합니다.
FSD의 핵심 개념
Layers (계층)
Slices (슬라이스)
비즈니스 도메인별로 코드를 분할하며, 같은 레이어 안에서 다른 슬라이스를 참조할 수 없습니다.
Segments (세그먼트)
목적에 따라 코드를 그룹화합니다:
ui
: UI 컴포넌트, 스타일 등api
: 백엔드 상호작용model
: 데이터 모델, 스키마, 비즈니스 로직lib
: 라이브러리 코드config
: 설정 파일과 기능 플래그FSD의 장점
BOFIT 팀의 변형된 FSD 구조
왜 변형했을까?
표준 FSD는 대규모 프로젝트에 최적화되어 있어, 초기 프로젝트에는 다소 복잡할 수 있습니다.
FSD 공식 문서에서도 "모든 레이어를 사용할 필요는 없다"고 언급하고 있어, 우리 프로젝트에 맞게 단순화했습니다.
우리가 선택한 구조
각 레이어의 역할
App 레이어
애플리케이션의 진입점과 전역 설정을 담당합니다.
Pages 레이어
실제 라우팅되는 페이지들을 관리하며, 화면의 전체 구조를 파악할 수 있습니다.
Widgets 레이어
각 도메인별로 해당 페이지에서 사용되는 모든 로직과 컴포넌트를 관리합니다.
이는 기능 중심의 관심사 분리를 실현하는 핵심 레이어입니다.
Shared 레이어
여러 도메인에서 공통으로 사용되는 컴포넌트, 훅, 유틸리티, API 등을 관리합니다.
이 구조의 장점
🏃🏻➡️ 빠르고 효율적인 Vite + Pnpm
좋은 프로젝트를 만들기 위해서는 빠른 개발 환경과 안정적인 의존성 관리가 필수라고 판단했습니다.
느린 빌드 시간이나 의존성 설치 시간은 특히 장기 해커톤 같은 환경에서 생산성을 크게 떨어뜨릴 수 있기 때문입니다.
그래서 BOFIT 팀은 빌드 도구와 개발 서버로 Vite, 패키지 매니저로는 Pnpm을 선택했습니다.
출처: vite 공식 홈페이지
Vite의 장점
코드 수정 시 변경된 부분만 빠르게 반영되어 개발 속도가 크게 향상됩니다.
개발 서버에서는 ESM 기반의 Native Module로 동작하고, 빌드시에는 Rollup을 사용해 최적화된 결과물을 제공합니다.
서버 시작 시간이 매우 짧아 반복적인 실행에 부담이 없습니다.
React, TypeScript, Vanilla Extract 등 최신 라이브러리와의 호환성이 매우 좋습니다.
Pnpm의 장점
Pnpm은 하드 링크 기반의 고유한 저장 구조를 사용하여 의존성을 한 번만 다운로드하고 여러 프로젝트에서 공유할 수 있습니다.
Pnpm은 전역에 공유 저장소(store) 를 유지하여 동일한 패키지를 다시 다운로드하지 않고도 여러 프로젝트에서 재사용할 수 있습니다.
이를 통해 디스크 사용량은 줄고, 설치 속도는 빨라집니다.
의존성 트리를 엄격하게 관리해 잘못된 중복 참조나 숨겨진 의존성 버그를 방지할 수 있습니다.
모노레포 환경에서 여러 패키지를 효율적으로 관리할 수 있고, Vite와 연계하여 빠른 빌드와 의존성 동기화를 할 수 있습니다.
Beta Was this translation helpful? Give feedback.
All reactions