관심사의 분리부터 빌드 성능 최적화까지: 위젯 엔진 패키지 이관 기록
🧐 위젯 시스템 마이그레이션: 왜 엔진을 격리했나?
최초 apps/web 내부에 구현했던 위젯 시스템은 서비스 규모가 커짐에 따라 점차 거대한 부채가 되어갔다. 로직이 비즈니스 코드와 뒤섞이며 관리가 어려워진 것은 물론, 운영 환경의 파편화가 결정적인 트리거가 되었다.
현재 프로젝트는 오가니제이션(EC2) 과 개인 포크 레포(Vercel) 라는 서로 다른 두 환경에서 돌아간다. 양쪽 모두에서 동일한 위젯 경험을 유지해야 했기에, 코드 중복을 제거하고 독립적인 실행 환경을 보장받기 위해 위젯 엔진을 별도의 내부 패키지(@helloworld/widget)로 분리하기로 했다.
🚀 왜 '내부 패키지(Internal Package)'인가?
외부 npm 배포도 고려했지만, 모노레포 환경을 십분 활용할 수 있는 Internal Package 방식이 현시점에선 가장 합리적이라고 판단했다.
- 원자적 커밋(Atomic Commits): 앱과 패키지의 변경사항을 하나의 PR로 관리할 수 있어 히스토리 파악이 용이하다.
- 개발 경험(DX): 수정 사항이 런타임에 즉시 반영되는 Hot Reload를 포기할 수 없었다.
- 의존성 확장성: 공통
typescript-config나tailwind-config를 그대로 상속받아 일관성을 유지하기 쉽다.
🏗️ 설계 전략: 관심사의 분리 (SoC)
단순한 파일 이동이 목표가 아니었다. "프레임워크(틀)" 와 "콘텐츠(알맹이)" 를 엄격히 분리하는 아키텍처를 세웠다.
- Core Framework:
dnd-kit을 활용한 드래그 앤 드롭, 그리드 시스템,Zustand기반의 상태 관리 등 위젯의 핵심 구동 원리를 담당한다. - Implementation: 달력, 달성률 등 실제 화면에 그려질 개별 UI 컴포넌트들이다.
- Orchestrator: 메인 앱에서 복잡한 호출 없이 대시보드를 바로 불러올 수 있게 해주는
WidgetDashboard컴포넌트다.
🛠️ 마이그레이션 기록: 4단계의 프로세스
1단계: 파운데이션 (Types & Constants)
가장 먼저 의존성 관계가 없는 순수 타입과 상수를 도려냈다. 특히 WidgetId를 리터럴 유니온 타입으로 정의하여 전역적인 타입 안정성을 확보하는 데 집중했다.
// packages/widget/src/types/index.ts
export type WidgetId = 'calendar' | 'problemStatus' | 'monthlyTop' |




