||
안녕하세요. 서비스웹개발팀의 헨비입니다.
이번 글에서는 React와 Vue를 함께 지원하는 디자인 시스템을 직접 만들고 배포/운영하면서, 왜 결국 “모든 것을 동일하게 지원”하는 전략에서 “운영 가능한 범위를 선택하는 전략”으로 전환했는지 정리해보려고 합니다.
처음에는 core + react + vue 구조로 컴포넌트까지 일관되게 제공하는 것을 목표로 했지만, 실제 운영에서는 기능 추가, QA, 배포, 회귀 검증 비용이 빠르게 커졌습니다.
현재는 React 컴포넌트를 운영 축으로 두고, Vue에서는 토큰/스타일/유틸리티 같은 디자인 파운데이션을 중심으로 소비하고 있습니다. 이 전환 과정에서 무엇을 얻고 무엇을 포기했는지, 그리고 비슷한 상황에서 어떤 기준으로 선택해야 하는지 공유하겠습니다.
왜 멀티 프레임워크 디자인 시스템을 시작했나?
서비스웹개발팀은 레거시 Vue 프로젝트와 신규 React/Next.js 프로젝트를 동시에 운영하고 있습니다.
문제는 기술 스택 자체보다, YDS 업데이트를 프로젝트별로 반복 반영하는 운영 비용이 매우 컸습니다.
그래서 특정 당장 프레임워크 통일보다는 프레임워크가 달라도 공통 규칙을 재사용할 수 있는 구조를 먼저 찾았습니다.
검토 기준은 SSR, SEO, 복잡도, 런타임 의존성, 운영 유지비였습니다.
결론적으로 우리 팀 조건에서는 Headless Core + Framework Adapter가 가장 운영 가능한 선택이었습니다.
2. 초기 설계: core + adapter(react/vue) 구조
멀티 프레임워크 환경에서 중복 구현을 줄이기 위해 packages/core, packages/react, packages/vue로 구조를 분리했습니다.
core는 디자인 토큰/스타일 유틸리티/headless 로직의 단일 출처로 두었습니다.
react, vue는 프레임워크별 렌더링/이벤트/컴포넌트 표면만 담당하는 adapter로 설계했습니다.
// core (headless)
export function createBoxButton(props) {
return { state, bindings }
}
// React adapter (요약)
import { createBoxButton } from "@yeogiforge/core";
export function BoxButton(props) {
const { state, bindings } = createBoxButton(props);
return (
<button {...bindings} data-state={state}>
{props.children}
</button>
);
}
// Vue adapter (요약)
<script setup lang="ts">
import { createBoxButton } from "@yeogiforge/core";
const props = defineProps<{ disabled?: boolean }>();
const { state, bindings } = createBoxButton(props);
</script>
<template>
<button v-bind="bindings" :data-state="state">
<slot />
</button>
</template>
두 adapter는 같은 core 로직을 공유하고, 프레임워크별 렌더링 표면만 다르게 가져갑니다.
즉, 상태/의미 체계는 core에서 통일하고 UI 연결 지점만 React/Vue에서 분리했습니다.
핵심 의도는 “규칙은 한 곳에서 관리하고, 프레임워크별 연결만 분리”하는 것이었습니다.
초기에는 이 구조가 일관성과 확장성을 동시에 잡는 가장 현실적인 방법이라고 판단했습니다.
문제는 아키텍처 선택 자체보다, 이 구조를 실제 운영하면서 발생한 이중 구현과 동기화