디자인 시스템 마이그레이션: 레거시 CSS를 토큰 기반 시스템으로 전환하는 전략

디자인 시스템

디자인 시스템 마이그레이션디자인 토큰CSS 리팩토링레거시 전환프론트엔드

이 글은 누구를 위한 것인가

  • 수천 줄의 하드코딩 CSS를 디자인 토큰으로 전환해야 하는 프론트엔드 팀
  • 기존 서비스를 운영하면서 디자인 시스템을 처음 도입하는 팀
  • 마이그레이션 중 팀 저항과 일정 부담을 최소화하고 싶은 테크 리더

들어가며

"디자인 토큰을 도입하겠다"고 결정했다. 그런데 프로덕션에는 수십만 줄의 CSS가 있고, 하드코딩된 색상(#4A90E2, rgba(0,0,0,0.12))과 크기 값(14px, 1.5rem)이 수백 곳에 흩어져 있다. 어디서부터 시작해야 할까?

빅뱅 마이그레이션(한 번에 전부 전환)은 위험하다. 서비스 중단 없이 점진적으로 전환하는 전략이 필요하다. 이 글에서는 팀이 실제로 써온 레거시 CSS를 토큰 기반으로 전환하는 단계별 방법을 설명한다.

이 글은 bluefoxdev.kr의 디자인 토큰 도입 전략 을 참고하고, 레거시 마이그레이션 관점에서 확장하여 작성했습니다.


1. 마이그레이션 전 현황 파악

1.1 레거시 코드 감사 (Audit)

마이그레이션 전에 현재 상태를 정량화해야 한다.

# 프로젝트 내 하드코딩된 색상 값 수 파악
grep -r "#[0-9a-fA-F]\{3,6\}" src/ --include="*.css" --include="*.scss" | wc -l

# 고정 픽셀 값 파악
grep -r "[0-9]\+px" src/ --include="*.css" | grep -v "0px" | wc -l

# 중복 색상 값 찾기
grep -roh "#[0-9a-fA-F]\{6\}" src/ | sort | uniq -c | sort -rn | head -20

1.2 감사 결과를 토큰으로 매핑

발견된 색상 → 시맨틱 토큰 매핑 계획

#4A90E2 (23회 사용) → color.primary.500
#357ABD (8회 사용)  → color.primary.600
#F5F5F5 (45회 사용) → color.background.secondary
#333333 (67회 사용) → color.text.primary
#666666 (34회 사용) → color.text.secondary
#E5E5E5 (29회 사용) → color.border.default

2. 토큰 구조 설계

2.1 3계층 토큰 구조

Primitive Tokens (원시값)
  color.blue.50: #EFF6FF
  color.blue.500: #3B82F6
  color.blue.900: #1E3A8A
  space.1: 4px
  space.2: 8px

Semantic Tokens (의미 기반)
  color.primary.default: {color.blue.500}
  color.primary.hover: {color.blue.600}
  color.text.primary: {color.gray.900}
  space.component.padding.sm: {space.2}

Component Tokens (컴포넌트별)
  button.primary.background: {color.primary.default}
  button.primary.background.hover: {color.primary.hover}

2.2 CSS 변수로 출력

/* 자동 생성: tokens/output/variables.css */
:root {
  /* Primitive */
  --color-blue-50: #eff6ff;
  --color-blue-500: #3b82f6;
  
  /* Semantic */
  --color-primary-default: var(--color-blue-500);
  --color-text-primary: var(--color-gray-900);
  
  /* Component */
  --button-primary-bg: var(--color-primary-default);
}

[data-theme="dark"] {
  --color-text-primary: var(--color-gray-100);
  --color-background-default: var(--color-gray-900);
}

3. 점진적 마이그레이션 전략

3.1 Strangler Fig 패턴 적용

레거시 시스템을 즉시 교체하지 않고, 새 토큰 시스템이 점차 레거시를 대체하도록 한다.

[Phase 1] 토큰 정의 + 새 컴포넌트에만 적용
  - 새로운 기능 개발 시 반드시 토큰 사용
  - 기존 코드는 건드리지 않음

[Phase 2] 가장 자주 변경되는 컴포넌트 우선 마이그레이션
  - 버튼, 입력 필드, 카드 등
  - PR 단위로 작은 범위만 변경

[Phase 3] 자동화 스크립트로 대량 치환
  - 정규식 기반 하드코딩 → 변수 치환
  - 시각적 회귀 테스트로 변경 검증

[Phase 4] 레거시 값 완전 제거
  - 더 이상 사용되지 않는 하드코딩 값 lint 규칙 추가

3.2 자동 치환 스크립트

// scripts/migrate-colors.js
const fs = require('fs');
const path = require('path');
const glob = require('glob');

const colorMap = {
  '#4a90e2': 'var(--color-primary-default)',
  '#357abd': 'var(--color-primary-hover)',
  '#f5f5f5': 'var(--color-background-secondary)',
  '#333333': 'var(--color-text-primary)',
  '#666666': 'var(--color-text-secondary)',
  '#e5e5e5': 'var(--color-border-default)',
};

function migrateFile(filePath) {
  let content = fs.readFileSync(filePath, 'utf8');
  let changed = false;
  
  for (const [oldValue, newValue] of Object.entries(colorMap)) {
    const regex = new RegExp(oldValue.replace('#', '\\#'), 'gi');
    if (regex.test(content)) {
      content = content.replace(regex, newValue);
      changed = true;
    }
  }
  
  if (changed) {
    fs.writeFileSync(filePath, content);
    console.log(`✅ Migrated: ${filePath}`);
  }
}

const files = glob.sync('src/**/*.{css,scss}');
files.forEach(migrateFile);

4. 팀 저항 최소화 전략

4.1 흔한 반대 의견과 대응

반대 의견대응 방법
"지금도 잘 돌아가는데 왜 바꿔?"다크모드 추가, 브랜드 리뉴얼 시 비용 절감 데이터 제시
"마이그레이션 할 시간이 없다"새 개발에만 적용, 기존 코드는 점진적으로
"토큰 이름을 외워야 해?"자동완성 지원 IDE 플러그인, Figma에서 바로 확인
"버그 생기면 어떻게?"시각적 회귀 테스트(Chromatic)로 사전 검출

4.2 성공 지표 설정

KPI 예시:
- 토큰 커버리지: 전체 CSS 중 토큰 사용 비율 (목표: 80%)
- 하드코딩 색상 수: 마이그레이션 전 대비 감소율
- 테마 전환 시간: 새 테마 적용에 걸리는 개발 시간 (목표: 1시간 이내)
- 디자인-코드 불일치 버그: 분기별 감소율

5. Lint 규칙으로 회귀 방지

// .stylelintrc.json
{
  "rules": {
    "color-no-invalid-hex": true,
    "declaration-property-value-disallowed-list": {
      "color": ["/^#/", "/^rgb/"],
      "background-color": ["/^#/", "/^rgb/"],
      "border-color": ["/^#/", "/^rgb/"]
    }
  }
}

이 lint 규칙을 CI에 추가하면 새로운 하드코딩 색상이 PR에서 차단된다.


마무리: 마이그레이션 성공 조건

디자인 시스템 마이그레이션의 성공 조건은 기술이 아닌 사람과 프로세스다.

  1. 팀 공감대: 왜 해야 하는지 모두가 이해
  2. 작은 시작: 첫 PR은 버튼 하나의 색상 변수 교체만도 충분
  3. 자동화: 수작업 마이그레이션은 지속되지 않음
  4. 검증: 시각적 회귀 테스트로 신뢰 확보

레거시를 완전히 없애는 것이 목표가 아니다. 새로운 개발이 100% 토큰 기반으로 이루어지는 것이 첫 번째 목표다.