(1/2) 레거시 개발 환경에서 디자인 시스템 개편하기
신규 프로젝트를 시작할 때 프론트엔드에서는 React나 Vue 같은 컴포넌트 기반 프레임워크를 먼저 떠올리게 됩니다.
하지만 현업에서는 빠른 납기와 운영 제약 때문에, 이미 익숙한 레거시 스택을 유지한 채 신규 사업을 진행해야 하는 경우도 많습니다.
이 글은 Express + EJS + SCSS 환경이라는 전제를 유지한 상태에서, 짧은 개발 기간(4주) 동안 디자인 시스템을 점진적으로 이식하고 실제로 운영 가능하게 만든 경험을 정리한 기록입니다.
이 글(1부)에서는 이 개선안 중에서도 디자인 토큰을 중심으로 한 스타일 파이프라인 구축 과정에 집중합니다.
배경 및 문제점
전통적인 EJS/SCSS 기반 환경에서 UI를 개발하다 보면 다음과 같은 문제를 반복해서 마주하게 됩니다. 당시 프로젝트의 개발 환경은 다음과 같습니다.
개발 환경
- Backend: Node.js, Express.js, TypeScript
- Frontend: EJS, TypeScript, jQuery, SCSS
이 프로젝트의 프론트 환경은 UI 코드가 공용 partial(header, footer 등)과 페이지 단위의 서버 사이드 템플릿으로 나뉘어 관리되었습니다. 서비스가 되고나면 점점 아래와 같은 문제가 누적됩니다.
-
“정답 스타일”이 어디에 있는지 알 수 없다
여러 페이지에서 반복되는 버튼이나 카드 UI가 있어도, 무엇이 기준인지 확인할 곳이 없습니다.
결국 개발자마다 기억에 의존해 비슷하게 구현하게 되고, UI 일관성은 서서히 무너집니다. -
스타일이 파편화되고 하드코딩이 늘어난다
페이지 단위 템플릿 구조에서는 공통 스타일보다 페이지 전용 스타일을 만드는 것이 더 빠르게 느껴집니다.
하지만 시간이 지나면 “조금씩 다른 유사 컴포넌트”가 쌓이며 수정 비용이 감당하기 어려운 수준으로 커집니다. -
디자인 시스템이 문서로만 남는다
Figma에 훌륭한 가이드가 있어도 코드에서 바로 사용할 수 없다면, 개발자는 결국 값을 복사해 붙여넣는 방식으로 돌아가게 됩니다.
이 문제들의 공통점은 디자인과 코드 사이에 공유되는 기준점이 없다는 것이었습니다.
개선안 정의
기존 개발 흐름을 유지하면서도 스타일의 일관성과 변경 안정성을 높이기 위해 다음과 같은 개선안을 수립했습니다.
-
토큰을 ‘코드가 소비하는 데이터 규격’으로 정의한다
디자인 기준(Figma)이 JSON을 거쳐 코드가 이해할 수 있는 언어(SCSS/CSS)로 자동 변환되도록 합니다.
개발자는 속성 값 수치를 하나하나 입력하는 것이 아닌 의미 있는 이름만 사용합니다. -
반응형과 스케일 정책을 중앙화한다
모바일과 PC 간의 간격 차이 같은 복잡한 계산은 토큰 시스템이 책임집니다.
개발자는 “12번 간격”이라는 키워드만 사용합니다. -
아이콘과 UI 기준을 한 곳에서 관리한다
아이콘을 여기저기 복사하지 않고 레지스트리 형태로 관리합니다. -
확인 가능한 가이드 페이지를 제공한다
/guide같은 가이드 페이지를 만들어 “현재 코드 기준의 정답 UI”를 항상 확인할 수 있게 합니다. (스토리북과 같은 역할)
이 중에서도 가장 핵심이 되는 것은 디자인 토큰을 중심으로 한 스타일 파이프라인 구축이었습니다.
1부에서 진행된 작업 파이프라인 구성도
1부에서는 디자인 기준이 실제 코드에 안전하게 반영되기까지의 전체 흐름을 하나의 파이프라인으로 정리했습니다.
디자이너는 Figma에서 토큰을 정의하고, 개발자는 이 토큰을 직접적인 숫자가 아닌 이름으로 사용합니다.
아래 그림은 1부에서 다룰 전체 흐름을 한눈에 보여줍니다.
- Figma에서 정의된 디자인 토큰
- Tokens Studio를 통한 JSON 추출
- JSON을 SCSS/CSS로 변환하는 빌드 파이프라인
- 변환된 토큰을 사용하는 실제 컴포넌트 코드
이후 섹션에서는 이 파이프라인의 각 단계를 하나씩 살펴보며, 레거시 환경에서 이 구조가 어떻게 작동하는지 구체적으로 설명합니다.
1. Tokens Studio for Figma를 통한 토큰 관리
피그마에 작성된 디자인 토큰을 추출을 위해 Tokens Studio for Figma라는 플러그인을 사용했습니다. 해당 플러그인을 사용하시면 간편하게 JSON 형태로 추출이 가능합니다.
참고: 이 과정이 실제로 어떻게 이루어지는지 영상으로 확인하고 싶으시다면 참조영상 링크를 확인해 보세요. (영상 1분 55초부터 4분 13초까지의 내용)
사실 토큰 추출 방법보다 더 중요한 것이 토큰을 어떻게 작성할 것인지가 중요합니다. 토큰 작성은 주로 디자이너가 담당하지만,
토큰의 구조와 네이밍 규칙은 프론트엔드 개발자가 함께 설계해야 합니다.
이 단계에서 정한 규칙이 이후 JSON 변환, SCSS 변수/맵 구성,
반응형·테마 처리 방식까지 모두 영향을 주기 때문입니다.
이 글에서는 토큰을 단순한 디자인 값이 아니라, 디자인과 코드가 공유하는 인터페이스 규격으로 정의했습니다.
예를 들어 padding.12라는 토큰이 있다면, 개발자는 실제 px 값이나 기기별 차이를 알 필요 없이 의미 있는 이름만 사용하면 됩니다. 기기별 계산과 스케일 정책은 토큰 시스템이 책임집니다.
‘2단계 토큰’ 설계 (Primitive / Semantic)
스타일 기준을 유연하게 관리하기 위해,
디자인 토큰을 Primitive(원시값) 와 Semantic(의미값)
두 단계로 나누어 설계했습니다.
Primitive 토큰은 색상, 숫자처럼 가장 기초적인 값입니다.
특정 UI에서의 의미를 담지 않고,
값 자체만 표현하는 것을 원칙으로 합니다.
- 예:
color.brand.950,number.12 - 직접 UI에서 사용하지 않음
반면 Semantic 토큰은 Primitive 토큰을 참조해,
실제 UI에서의 역할과 사용처를 이름으로 표현합니다.
- 예:
text.primary→ 기본 텍스트 색상padding.12→ 여백 스케일 정책
- 코드에서는 가급적 이 단계의 토큰만 사용
이렇게 역할 중심으로 토큰을 분리함으로써,
디자인 값이 바뀌더라도 코드의 의미와 구조는 그대로 유지할 수 있습니다.
특히 컴포넌트 추상화가 제한적인
EJS/SCSS 기반 레거시 환경에서는,
이 Semantic 토큰이 컴포넌트를 대신하는 최소 단위의 추상화 역할을 합니다.
테마·반응형 확장을 고려한 구조
이 구조의 장점은 “하나의 이름, 여러 개의 값”을 자연스럽게 표현할 수 있다는 점입니다.
예를 들어 text.primary라는 토큰은
라이트 모드와 다크 모드에서 서로 다른 색상을 참조하도록 구성할 수 있습니다.
이때 토큰 이름은 그대로 유지되고,
참조하는 값만 테마별로 달라집니다.
padding.12 역시 마찬가지입니다.
모바일과 PC에서 실제 여백 값은 달라질 수 있지만,
코드는 언제나 동일한 토큰 이름만 사용합니다.
이렇게 테마나 기기별 차이를
토큰 시스템 내부로 흡수함으로써,
스타일 정책이 변경되더라도 컴포넌트 코드에는 수정이 최소화됩니다.
2. 토큰 변환 파이프라인 (JSON → SCSS/CSS)
위에서 설계한 토큰들은 하나의 JSON 파일로 추출됩니다. 하지만 그대로 두고 쓰기에는, 다소 불편한 형태입니다. 디자인 토큰은 결국 코드가 소비해야 하는 데이터이기 때문에, 개발자가 쓰기 좋은 형태로 한 번 더 변환하는 과정이 필요했습니다.
그리하여 다음과 같은 목표를 정했습니다.
- 토큰의 이름(의미) 은 그대로 유지할 것
- 사용 목적에 맞는 표현 형태로만 변환할 것
- 반응형·테마 같은 정책은 토큰 시스템 내부에 숨길 것
이를 위해 추출된 JSON을 Node.js 스크립트로 가공해, 토큰을 다음 세 가지 형태로 변환하는 파이프라인을 구성했습니다.
1) SCSS 변수 ($...)
컴파일 시점에 값이 확정되는 토큰입니다.
- 색상, 폰트 크기처럼 변하지 않는 값
- 자동완성과 오타 방지에 유리
- 기존 SCSS 코드에 가장 자연스럽게 녹아듦
2) SCSS Map
기기나 조건에 따라 값이 달라지는 토큰을 위한 구조입니다.
- 모바일 / PC처럼 분기가 필요한 경우
- 반응형 정책을 한 곳에 모아 관리
- 믹스인과 함께 사용해 분기 로직을 캡슐화
3) CSS 변수 (var(--ds-...))
테마 토큰은 CSS 변수 형태로 변환했습니다. 이유는 테마 전환이 런타임에 즉시 반영되어야 했기 때문입니다.
SCSS 변수는 컴파일 시점에 값이 고정되기 때문에 테마가 바뀔 때마다 스타일을 다시 빌드해야 합니다.
반면 CSS 변수는 브라우저 런타임에서 값이 결정되므로, HTML 속성만 변경해도 테마를 즉시 전환할 수 있습니다.
이 프로젝트에서는 data-theme 속성과 CSS 변수를 조합해 테마를 관리했습니다.
[data-theme="light"] {
--ds-color-text-primary: #151515;
}
[data-theme="dark"] {
--ds-color-text-primary: #f5f5f5;
}JSON → SCSS 변수 / Map 변환 로직
Tokens Studio에서 추출된 JSON은 중첩된 객체로 이루어진 트리 구조를 가집니다.
하지만 이 구조를 그대로 SCSS로 옮기면 중첩이 깊어지고 실제 스타일 코드에서 사용하기가 어렵습니다.
그래서 변환 단계에서는 토큰을 “트리”가 아닌 “경로(path)” 로 바라봅니다.
body → lg → medium → fontSize즉, 중첩된 객체 하나하나를 의미 있는 토큰 경로로 해석하고, 이 경로를 기준으로 코드를 생성합니다.
SCSS 변수 변환 로직
SCSS 변수로 변환되는 대상은 실제 값을 가진 leaf 토큰입니다.
변환 흐름은 다음과 같습니다.
- JSON 전체를 순회한다
- value와 type을 동시에 가진 노드만 처리한다
- 토큰 경로를 -로 이어 하나의 변수명으로 평탄화한다
- 토큰 타입에 따라 값의 포맷을 결정한다
// body.lg.medium.fontSize
// → $body-lg-medium-font-size
const varName = `$${path.join('-')}`;
const value = type === 'fontSizes' ? `${raw}px` : raw;이 과정을 거치면 복잡한 JSON 구조는 사라지고, SCSS에서 바로 사용할 수 있는 변수만 남습니다.
$body-lg-medium-font-size: 15px;
$brand-base: #0052e9;SCSS Map 변환 로직 (반응형 토큰)
여백, radius처럼 기기별로 값이 달라지는 토큰은 단일 SCSS 변수로 표현하기 어렵습니다.
예를 들어 padding.12라는 토큰은 의미는 하나지만 실제 값은 기기에 따라 달라집니다.
- Mobile: 0.8rem
- PC: 1.2rem
이런 경우를 위해 반응형 토큰은 SCSS Map 형태로 변환했습니다.
$semantic-number: (
padding: (
12: (
base: 0.8rem,
md: 1.2rem,
),
),
);이 구조의 핵심은 다음과 같습니다.
- 토큰의 의미(
padding.12)는 그대로 유지 - 기기별 값 차이는 map 내부로 숨김
- 실제 스타일 코드에서는 map 구조를 직접 사용하지 않음
SCSS 헬퍼 함수로 토큰 사용 단순화하기
SCSS Map은 반응형 토큰을 표현하기 위한 내부 구현 구조입니다. 실제 스타일 코드에서는 헬퍼 함수와 믹스인을 통해 이 복잡함을 감춥니다.
@include ds.ds-prop(
padding,
ds.sem-number(padding, 12)
);이 한 줄은 다음을 모두 처리합니다.
- 현재 기기에 맞는 값 선택
- 필요한 미디어 쿼리 생성
- CSS 속성으로 출력
개발자는 단순히 “어떤 정책을 쓸지”만 선언니다.
3. 테마 적용 및 활용
앞에서 만든 토큰 구조와 변환 파이프라인은 결국 컴포넌트 코드가 얼마나 단순해지는지로 증명됩니다.
이제 실제 스타일 코드에서 이 토큰들이 어떻게 사용되는지 살펴보겠습니다.
이 프로젝트에서는 HTML의 data-theme 속성과 CSS 변수를 조합해 테마 전환을 처리했습니다.
SCSS에서의 활용 예시
전용 헬퍼 함수를 사용해 하드코딩 없이 토큰을 적용합니다.
.card {
// 테마 변수 사용
color: var(--ds-text-primary);
// 반응형 패딩 적용 (12번 간격 정책 호출)
@include ds.ds-prop(padding, ds.sem-number(padding, 12));
background-color: var(--ds-surface-1);
}1부 요약
이 글의 1부에서는 Express + EJS + SCSS라는 레거시 환경을 유지한 채로, 디자인 기준이 실제 코드에 안전하게 전달되는 토큰 기반 스타일 파이프라인을 구축하는 데 집중했습니다.
디자인 토큰을 단순한 문서나 참고 값이 아니라, 코드가 직접 소비하는 데이터 규격으로 정의하고, Figma → JSON → SCSS/CSS로 이어지는 변환 흐름을 통해 디자인의 변경이 예측 가능한 방식으로 반영되도록 만들었습니다.
그 결과, 컴포넌트 코드는 픽셀이나 색상 값을 직접 알 필요 없이 의미 있는 이름(토큰)만 사용하게 되었고, 반응형과 테마 같은 복잡한 정책은 토큰 시스템 내부에 자연스럽게 캡슐화되었습니다.
2부에서는
이 1부에서 구축한 토큰 기반 파이프라인 위에 아이콘 시스템과 실제 UI 컴포넌트를 구현하고, 이를 운영 환경에서 검증하고 공유하기 위한 가이드 페이지를 만드는 과정을 이어서 다룹니다.
샘플 코드
이번 1~2부에서 다룬 내용은 샘플 코드로도 확인할 수 있습니다.