SVG 아이콘 — 코드 배포 없이 프로덕트 팀이 직접 관리하는 법

6 min read
SVGCSS아이콘프론트엔드DX
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로 색을 제어한다.

styles/mask-icon.module.css
.maskIcon {
  background-color: currentColor;
  mask-repeat: no-repeat;
  mask-position: center;
  mask-size: contain;
}
components/SvgIcon.tsx
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 변수로 브랜드 색상을 정의해두면:

styles/categories.css
[data-category="auto"] {
  color: #e84c00;
}
 
[data-category="immobili"] {
  color: #00b8a9;
}

같은 SVG 파일이 카테고리에 따라 다른 색으로 렌더링된다. S3의 파일을 교체하면 다음 요청부터 바로 반영. 배포 필요 없음.

두 가지 전략의 조합

Subito는 아이콘 유형에 따라 전략을 나눈다.

아이콘 종류전략이유
배지, 마케팅 아이콘S3 + mask-image자주 바뀜, 비개발자가 관리
UI 시스템 아이콘SVGR + 번들링코드와 함께 버전 관리, HTTP 요청 없음

정적인 아이콘은 SVGRSVGO로 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 속성을 정리한다.

CSS모던 CSS

envscan — .env.example을 손으로 관리하지 말자

코드에서 process.env 참조를 스캔해 .env.example을 자동 생성하는 envscan의 접근 방식과 기존 도구들과의 차이를 정리한다.

Node.js환경 변수

모던 CSS 컴포넌트 아키텍처 — 네이티브 기능만으로 설계하는 컴포넌트 시스템

CSS Nesting, Cascade Layers, Container Queries, :has() 등 네이티브 CSS 기능으로 컴포넌트 기반 아키텍처를 구축하는 방법을 정리한다.

CSSContainer Queries

CSS @property — 커스텀 속성에 타입을 부여하는 방법

CSS @property at-rule로 커스텀 속성에 타입 정의, 상속 제어, 폴백을 추가해 렌더링 안정성과 애니메이션 가능성을 확보하는 방법을 다룬다.

CSS@property

CSS :near() — 마우스가 '가까이' 오면 반응하는 새로운 의사 클래스

CSS Working Group에 제안된 :near() 의사 클래스는 포인터 근접성을 감지해 호버 전에 UI를 활성화하는 새로운 상호작용 패턴을 연다.

CSS:near()

CSS만으로 커스텀 셀렉트 박스 — JavaScript 150줄이 사라지는 순간

Chrome 135에 도입된 appearance: base-select와 sibling-index()로 JavaScript 없이 완전한 커스텀 드롭다운을 구현하는 방법을 분석한다.

CSSbase-select

sibling-index()로 만드는 CSS 스크롤 소용돌이 — JavaScript 없이 수백 개 요소 애니메이션

CSS sibling-index()와 scroll-driven animations를 결합해 순수 CSS만으로 텍스트 보텍스 효과를 구현하는 기법을 다룬다.

CSSsibling-index

Interop 2026 — 브라우저 전쟁이 끝나고 표준 전쟁이 시작됐다

Chrome, Safari, Firefox가 합의한 20개 웹 표준 집중 영역과 프론트엔드 개발자가 주목해야 할 핵심 기능을 정리한다.

InteropCSS