SVG 아이콘 — 코드 배포 없이 프로덕트 팀이 직접 관리하는 법
아이콘 하나 바꾸는 데 PR이 필요하다면, 뭔가 잘못된 거다.
마케팅 팀이 "배지 아이콘 색상 바꿔주세요"라고 요청할 때마다 개발자가 파일 교체하고, PR 올리고, 배포 기다리는 구조. 이탈리아의 중고거래 플랫폼 Subito가 이 문제를 CSS 한 줄로 해결한 방법을 살펴보자.
문제는 단순하다
Subito는 셀러 품질을 나타내는 배지 아이콘("빠른 응답자", "인증된 셀러" 등)을 카테고리 컨텍스트에 따라 동적으로 색상을 바꿔야 했다. 카테고리마다 브랜드 색이 다르기 때문이다.
기존 방식의 문제:
<img>태그: 색상 제어 불가- 인라인 SVG: 매번 코드 변경 필요, 배포 필수
- 폰트 아이콘: 새 아이콘 추가 시 폰트 파일 빌드 필요
어떤 방식이든 아이콘 하나 바꾸려면 개발자가 끼어야 했다.
해결책: S3 + CSS mask-image
두 가지 조합으로 문제를 해결했다.
1단계: S3 호스팅
SVG 파일을 퍼블릭 S3 버킷에 업로드한다. 프로덕트 팀은 콘솔에서 직접 파일을 교체할 수 있다. 개발자 개입 없이.
2단계: CSS mask-image로 동적 색상 제어
여기가 핵심이다. mask-image 속성은 SVG를 투명도 마스크로 취급한다. SVG의 실제 fill 색상 대신, background-color로 색을 제어한다.
.maskIcon {
background-color: currentColor;
mask-repeat: no-repeat;
mask-position: center;
mask-size: contain;
}const ASSETS = 'https://cdn.subito.it';
const maskStyle = (file: string): React.CSSProperties => ({
maskImage: `url(${ASSETS}/static/icons/badges/${file}.svg)`,
});
export const SvgIcon = ({ iconName, className }: Props) => (
<span
className={classNames(styles.maskIcon, className)}
style={maskStyle(iconName)}
aria-hidden="true"
/>
);background-color: currentColor가 핵심이다. 부모 요소의 color 속성을 그대로 상속받아서, CSS 변수 하나만 바꾸면 모든 카테고리 색상이 자동으로 적용된다.
[💡 잠깐! 이 용어는?] CSS mask-image: 요소를 마스크 이미지의 투명도에 따라 자르는 CSS 속성. SVG를 마스크로 쓰면 SVG 형태는 유지하면서 색상만 CSS로 제어 가능.
실제 동작 방식
카테고리마다 CSS 변수로 브랜드 색상을 정의해두면:
[data-category="auto"] {
color: #e84c00;
}
[data-category="immobili"] {
color: #00b8a9;
}같은 SVG 파일이 카테고리에 따라 다른 색으로 렌더링된다. S3의 파일을 교체하면 다음 요청부터 바로 반영. 배포 필요 없음.
두 가지 전략의 조합
Subito는 아이콘 유형에 따라 전략을 나눈다.
| 아이콘 종류 | 전략 | 이유 |
|---|---|---|
| 배지, 마케팅 아이콘 | S3 + mask-image | 자주 바뀜, 비개발자가 관리 |
| UI 시스템 아이콘 | SVGR + 번들링 | 코드와 함께 버전 관리, HTTP 요청 없음 |
정적인 아이콘은 SVGR과 SVGO로 React 컴포넌트로 변환해 번들에 포함시킨다. 매번 HTTP 요청이 발생하지 않아서 성능상 유리하다.
[💡 잠깐! 이 용어는?]
SVGR: SVG 파일을 React 컴포넌트로 변환해주는 도구. import { ReactComponent as Icon } from './icon.svg' 형태로 사용.
이 패턴이 유효한 조건
이 접근이 효과적인 경우:
- 아이콘이 자주 바뀐다
- 비개발자가 아이콘을 관리해야 한다
- 동적 색상 제어가 필요하다
- CDN 응답 속도가 충분히 빠르다
반대로 아이콘이 거의 안 바뀌고 성능이 최우선이라면, 번들에 포함시키는 게 낫다. 상황에 맞는 도구를 고르는 게 전부다.
마무리
"코드 없이 콘텐츠 관리"라는 개념을 아이콘에 적용한 사례다. CSS mask-image는 브라우저 지원도 충분하고(Chromium, Safari, Firefox 모두 지원), 구현 복잡도도 낮다. 프로덕트 팀이 직접 아이콘을 관리할 수 있게 되면, 개발자는 정말 중요한 일에 집중할 수 있다.
참고:
관심 있을 만한 포스트
CSS 한 줄로 업그레이드 — 지금 바로 적용할 수 있는 12가지 모던 CSS 속성
aspect-ratio부터 scrollbar-gutter까지, 한 줄 추가로 스타일시트를 현대화하는 12가지 CSS 속성을 정리한다.
envscan — .env.example을 손으로 관리하지 말자
코드에서 process.env 참조를 스캔해 .env.example을 자동 생성하는 envscan의 접근 방식과 기존 도구들과의 차이를 정리한다.
모던 CSS 컴포넌트 아키텍처 — 네이티브 기능만으로 설계하는 컴포넌트 시스템
CSS Nesting, Cascade Layers, Container Queries, :has() 등 네이티브 CSS 기능으로 컴포넌트 기반 아키텍처를 구축하는 방법을 정리한다.
CSS @property — 커스텀 속성에 타입을 부여하는 방법
CSS @property at-rule로 커스텀 속성에 타입 정의, 상속 제어, 폴백을 추가해 렌더링 안정성과 애니메이션 가능성을 확보하는 방법을 다룬다.
CSS :near() — 마우스가 '가까이' 오면 반응하는 새로운 의사 클래스
CSS Working Group에 제안된 :near() 의사 클래스는 포인터 근접성을 감지해 호버 전에 UI를 활성화하는 새로운 상호작용 패턴을 연다.
CSS만으로 커스텀 셀렉트 박스 — JavaScript 150줄이 사라지는 순간
Chrome 135에 도입된 appearance: base-select와 sibling-index()로 JavaScript 없이 완전한 커스텀 드롭다운을 구현하는 방법을 분석한다.
sibling-index()로 만드는 CSS 스크롤 소용돌이 — JavaScript 없이 수백 개 요소 애니메이션
CSS sibling-index()와 scroll-driven animations를 결합해 순수 CSS만으로 텍스트 보텍스 효과를 구현하는 기법을 다룬다.
Interop 2026 — 브라우저 전쟁이 끝나고 표준 전쟁이 시작됐다
Chrome, Safari, Firefox가 합의한 20개 웹 표준 집중 영역과 프론트엔드 개발자가 주목해야 할 핵심 기능을 정리한다.