SVG는 웹에서 어떻게 쓰고 관리할까요?

이 글에서 다루는 내용
SVG의 특징과 장단점을 먼저 살펴본 뒤, <img>·인라인 SVG·CSS 배경·React 컴포넌트·스프라이트로 사용하는 방법을 비교합니다. 이어서 SVGO와 SVGR로 파일을 최적화하고, 아이콘의 이름과 저장 위치를 정해 디자인 단계부터 빌드 과정까지 관리하는 방법을 다룹니다.
SVG의 특징
SVG: Scalable Vector Graphics | MDN
SVG(Scalable Vector Graphics)는 XML 기반의 벡터 그래픽 파일 형식으로, 선, 도형, 텍스트를 수학적 정의를 통해 표현합니다. 이 특징 덕분에 해상도와 관계없이 선명하게 렌더링되며, 웹 환경에서 특히 많이 사용됩니다.
SVG가 많이 사용되는 이유와 장점
-
해상도 독립성
벡터 경로로 표현하므로 확대해도 픽셀이 드러나지 않습니다.
-
파일 크기 효율성
도형이 단순하면 같은 크기의 PNG나 JPEG보다 파일이 작을 수 있습니다.
-
스타일링과 인터랙션의 유연성
인라인 SVG는 CSS와 JavaScript로 내부 요소의 색상과 상태를 바꿀 수 있습니다.
-
접근성 정보 제공
<title>·<desc>와 ARIA 속성을 넣을 수 있지만, 접근 가능한 이름은 사용 방식에 맞게 직접 제공해야 합니다. -
크로스 플랫폼 호환성
주요 브라우저가 SVG 표시를 지원합니다.
SVG의 단점
-
복잡한 이미지 표현의 비효율성
사진처럼 픽셀 단위의 세부 묘사가 필요한 경우, SVG는 비효율적이며 용량이 커질 수 있습니다.
-
스크립트 실행과 외부 리소스 로드 위험
신뢰할 수 없는 SVG를 DOM에 삽입하기 전에는 허용할 요소와 속성을 기준으로 정제해야 합니다.
// 신뢰할 수 없는 SVG를 허용 목록에 따라 정제합니다.import DOMPurify from "dompurify";const sanitizedSVG = DOMPurify.sanitize(rawSVG); -
브라우저 렌더링 성능 이슈
지나치게 복잡한 SVG는 렌더링 성능 저하를 일으킬 수 있습니다.
웹에서 SVG를 사용하는 방법
1) Inline SVG
HTML에 직접 SVG 코드를 삽입하여 사용하는 방식입니다.
<svg width="100" height="100">
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
</svg>
- 장점: CSS와 JavaScript로 내부 요소의 스타일과 동작을 제어할 수 있습니다.
- 단점: SVG 코드가 HTML에 포함되므로 문서 크기가 커질 수 있습니다.
2) <img> 태그로 사용
SVG 파일을 외부 리소스로 불러오는 방식입니다.
<img src="icon.svg" alt="Icon" />
- 장점: 파일 경로와 대체 텍스트만 지정하면 됩니다.
- 단점: 문서 밖의 이미지로 불러오므로 내부 요소를 CSS로 직접 제어하기 어렵습니다.
3) CSS background-image로 사용
CSS를 활용하여 SVG를 배경으로 설정합니다.
.my-icon {
background-image: url("icon.svg");
}
- 장점: 장식용 배경에 적합합니다.
- 단점: 대체 텍스트를 제공할 수 없으므로 의미 있는 콘텐츠에는 사용하지 않습니다.
4) React 컴포넌트로 사용
SVG를 React 컴포넌트로 가져와 JSX에서 직접 사용합니다.
import { ReactComponent as Icon } from "./icon.svg";
function App() {
return <Icon width={100} height={100} />;
}
- 장점: props로 크기와 색상을 제어하며 재사용할 수 있습니다.
- 단점: 많은 SVG를 정적으로 가져오면 JavaScript 번들이 커질 수 있습니다.
SVG 최적화
디자인 도구가 넣은 메타데이터·중복 속성·과도한 숫자 정밀도를 줄일 수 있습니다. 변경 후에는 모양·접근성 이름·색상 규칙이 유지되는지 확인합니다.
1) SVG 코드 최적화
SVG는 XML 기반이라 사람이 읽을 수 있는 텍스트로 되어 있지만, 그 때문에 불필요한 태그와 속성이 함께 들어가는 경우가 많습니다. 최적화를 통해 파일 크기를 줄이고 렌더링 성능을 개선할 수 있습니다.
불필요한 태그와 속성 제거
SVG 파일은 디자인 툴에서 내보낼 때 불필요한 메타데이터, 빈 태그, 기본값과 중복된 속성을 포함하는 경우가 많습니다. 이를 제거하면 크기를 줄일 수 있습니다.
최적화 전:
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="500px"
height="500px"
viewBox="0 0 500 500"
>
<title>Example SVG</title>
<desc>Created in Adobe Illustrator</desc>
<g>
<rect x="0" y="0" width="500" height="500" fill="white" />
<circle
cx="250"
cy="250"
r="200"
fill="#FF0000"
stroke="#000000"
stroke-width="1"
></circle>
</g>
</svg>
최적화 후:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
<circle
cx="250"
cy="250"
r="200"
fill="red"
stroke="black"
stroke-width="1"
/>
</svg>
이 예제에서는 xmlns:xlink, width, height를 없애고 색상 표기를 줄였습니다. <title>·<desc>·<rect>도 삭제했지만, 이는 접근성 설명과 흰 배경을 없애므로 원본과 의미가 같은 최적화는 아닙니다. 실제로 필요한 제목·설명·배경은 유지해야 합니다.
줄바꿈과 공백에 따라 바이트 수는 달라집니다. 크기만 비교하기 전에 투명 배경에서도 모양이 같은지, 접근성 이름과 설명이 남아 있는지 확인해야 합니다.
숫자를 소수점 이하 몇 자리까지만 유지
SVG에서 좌표값이나 크기를 나타내는 숫자가 과도하게 길어지면 파일 크기가 증가합니다. 소수점 이하의 불필요한 자릿수를 줄이면 크기를 줄이고 렌더링을 단순화할 수 있습니다.
최적화 전:
<path d="M0.0001 0.0001 L100.12345 200.98765 Z" />
최적화 후:
<path d="M0 0 L100.12 200.99 Z" />
위 예제에서는 소수점 둘째 자리까지 남겼습니다. 필요한 정밀도는 viewBox와 확대 비율에 따라 달라지므로 실제 크기로 비교합니다.
기본값과 동일한 속성 삭제
SVG 속성 중에는 기본값이 정해져 있는 것들이 있습니다. fill-opacity와 stroke-opacity의 기본값은 1이므로, 값이 1이라면 속성 자체를 삭제해도 렌더링 결과가 동일합니다. SVGO의 removeUnknownsAndDefaults 플러그인이 수행하는 최적화이기도 합니다.
최적화 전:
<path stroke-width="2" fill-opacity="1" stroke-opacity="1" />
최적화 후:
<path stroke-width="2" />
이 예제에서는 기본값과 같은 fill-opacity="1"과 stroke-opacity="1"을 삭제했습니다.
viewBox와 width/height 설정
SVG에서 width와 height를 설정하지 않고 viewBox만 남겨 두면, SVG를 다양한 크기로 재사용하기 좋습니다.
<svg viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" />
</svg>
2) 자동화된 도구 사용
수동으로 최적화하는 것도 가능하지만, SVGO와 같은 도구를 사용하면 반복 작업을 자동화할 수 있습니다.
SVGO 설정 예시
다음 설정은 제목과 설명을 제거하고 모든 색을 currentColor로 바꾸므로, 접근 가능한 이름을 사용하는 쪽에서 제공하는 단색 장식 아이콘에만 적용합니다.
svgo.config.js 파일:
module.exports = {
plugins: [
{ name: "removeTitle", active: true }, // <title> 태그 제거
{ name: "removeDesc", active: true }, // <desc> 태그 제거
{ name: "convertColors", params: { currentColor: true } }, // 모든 fill/stroke를 currentColor로 치환해 CSS color를 상속받게 한다
{ name: "cleanupNumericValues", params: { floatPrecision: 2 } }, // 숫자 소수점 자리수 제한
{ name: "removeUnusedNS", active: true }, // 사용하지 않는 네임스페이스 제거
{ name: "removeMetadata", active: true }, // 메타데이터 제거
{ name: "convertPathData", active: true }, // 경로 데이터 최적화
],
};
여기서 convertColors의 currentColor: true 옵션은 색상을 축약하는 것이 아니라, 모든 fill/stroke 값을 currentColor로 치환해 CSS의 color 속성을 상속받게 만드는 옵션입니다. 단색 아이콘을 CSS로 제어할 때 유용하지만, 다색 아이콘에 적용하면 원본 색상이 사라져 결과물이 달라지므로 주의해야 합니다.
최적화 전:
<svg
xmlns="http://www.w3.org/2000/svg"
width="200"
height="200"
viewBox="0 0 200 200"
>
<title>Circle Example</title>
<desc>Illustrator Export</desc>
<circle
cx="100"
cy="100"
r="50"
fill="#ff0000"
stroke="#000000"
stroke-width="1.123456"
/>
</svg>
위 설정을 적용한 결과 예시는 다음과 같습니다. 도구 버전과 플러그인 구성에 따라 출력 형식은 달라질 수 있습니다.
<svg
xmlns="http://www.w3.org/2000/svg"
width="200"
height="200"
viewBox="0 0 200 200"
>
<circle
cx="100"
cy="100"
r="50"
fill="currentColor"
stroke="currentColor"
stroke-width="1.12"
/>
</svg>
최적화 결과:
제목과 설명은 제거되고 색은 currentColor로 바뀌며 숫자 정밀도는 두 자리로 줄었습니다. 이 설정에는 크기를 제거하는 플러그인이 없으므로 width와 height는 남습니다. 접근 가능한 이름을 지운 단색 아이콘이라면 사용하는 쪽에서 이름을 제공해야 합니다.
SVGO는 svgo-loader 같은 로더를 통해 Webpack과 통합할 수 있습니다.
module.exports = {
module: {
rules: [
{
test: /\.svg$/,
use: [
{
loader: "svgo-loader",
options: {
plugins: [
{ name: "removeTitle", active: true },
{ name: "convertColors", active: true },
],
},
},
],
},
],
},
};
Figma와 연동
디자이너가 Figma에서 SVG를 내보낼 때, SVGO를 적용하여 최적화된 상태로 개발자에게 전달하도록 자동화 스크립트를 구성할 수 있습니다.
3) React 컴포넌트로 관리
SVG를 React 컴포넌트로 변환하면 스타일, 크기, 클래스 등 다양한 속성을 Props로 전달받아 동적으로 제어할 수 있어 재사용성과 유지보수성이 높아집니다.
SVG를 React 컴포넌트로 변환
SVG 파일을 ReactComponent로 임포트하여 JSX에서 직접 사용합니다. 아래 문법은 SVGR 등 해당 export를 만드는 빌드 설정이 필요하며 React의 기본 기능은 아닙니다.
import { ReactComponent as Icon } from "./icon.svg";
const App = () => (
<div>
<Icon width={50} height={50} fill="blue" />
</div>
);
export default App;
Props를 활용한 동적 제어
const Icon = ({ width = 24, height = 24, fill = "currentColor" }) => (
<svg
width={width}
height={height}
fill={fill}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
>
<circle cx="12" cy="12" r="10" />
</svg>
);
const App = () => <Icon width={40} height={40} fill="red" />;
export default App;
장점
- 재사용성: 같은 컴포넌트를 여러 화면에서 재사용할 수 있습니다.
- 스타일링:
fill,stroke,className같은 props로 스타일을 제어할 수 있습니다. - 모듈화: SVG 자산과 사용 인터페이스를 한 컴포넌트로 관리할 수 있습니다.
단점
- 번들 크기: 많은 SVG를 React 컴포넌트로 정적으로 가져오면 번들 크기가 커질 수 있습니다.
- 사용하지 않는 코드: 모든 아이콘을 정적으로 import하면 사용하지 않는 코드도 번들에 포함될 수 있습니다.
4) 필요할 때 불러오기
필요한 시점에만 SVG를 불러오면 초기 요청을 줄일 수 있지만 아이콘 표시가 늦어질 수 있습니다.
React dynamic import 사용
import React, { Suspense, lazy } from "react";
// URL과 named ReactComponent를 함께 내보내도록 설정한 로더 환경의 예제
const LazyIcon = lazy(() =>
import("./icon.svg").then((module) => ({ default: module.ReactComponent }))
);
const App = () => (
<Suspense fallback={<div>Loading...</div>}>
<LazyIcon />
</Suspense>
);
export default App;
Intersection Observer를 활용한 로드
이 예제에서는 요소가 뷰포트에 들어오면 이미지를 표시합니다.
import { useState, useEffect, useRef } from "react";
const LazyIcon = () => {
const [isVisible, setIsVisible] = useState(false);
const containerRef = useRef(null);
useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) setIsVisible(true);
});
const element = containerRef.current;
if (!element) return;
observer.observe(element);
return () => observer.disconnect();
}, []);
return (
<div ref={containerRef} style={{ width: 24, height: 24 }}>
{isVisible && <img src="icon.svg" alt="Lazy Icon" width="24" height="24" />}
</div>
);
};
export default LazyIcon;
SVGR를 쓰면 SVG 파일을 props를 받는 React 컴포넌트로 변환할 수 있습니다.
장점
- 초기 요청 감소: 화면에 필요한 시점까지 SVG 청크나 이미지 요청을 미룰 수 있습니다.
- 사용량에 따른 로드: 실제로 표시되는 자산만 불러올 수 있습니다.
단점
- 표시 지연: 사용자 인터랙션 뒤에 요청하면 아이콘이 늦게 보일 수 있습니다.
- 구현 복잡성: Intersection Observer나 로딩 상태를 관리하는 코드가 추가됩니다.
⚠️ React Suspense와 함께 사용할 때 주의하세요.
SVG 컴포넌트를 동적으로 가져오면 상위
<Suspense>의 fallback이 표시될 수 있습니다. 아이콘이 작고 수가 적다면 정적 import와 번들 크기를 비교합니다.
5) Sprite 사용
SVG 스프라이트(Sprite)는 여러 SVG 아이콘을 한 파일로 합쳐, 클라이언트가 네트워크 요청 한 번으로 필요한 아이콘을 불러오게 하는 방법입니다.
스프라이트 파일 생성
여러 SVG를 하나로 병합합니다.
<!-- sprite.svg -->
<svg xmlns="http://www.w3.org/2000/svg">
<symbol id="icon-home" viewBox="0 0 24 24">
<path d="M12 2L2 12h3v8h6v-6h2v6h6v-8h3L12 2z" />
</symbol>
<symbol id="icon-user" viewBox="0 0 24 24">
<path
d="M12 12c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm0 2c-4.41 0-8 3.59-8 8h2c0-3.31 2.69-6 6-6s6 2.69 6 6h2c0-4.41-3.59-8-8-8z"
/>
</symbol>
</svg>
스프라이트 사용
<use> 태그를 활용하여 원하는 아이콘을 로드합니다.
<svg class="icon">
<use href="sprite.svg#icon-home"></use>
</svg>
SVG 스프라이트와 Webpack 통합
const SpriteLoaderPlugin = require("svg-sprite-loader/plugin");
module.exports = {
module: {
rules: [
{
test: /\.svg$/,
loader: "svg-sprite-loader",
},
],
},
plugins: [new SpriteLoaderPlugin()],
};
장점
- 네트워크 요청: 여러 SVG를 하나로 묶어 한 파일로 요청할 수 있습니다.
- 재사용성: 같은 스프라이트 파일에서 여러 아이콘을 참조할 수 있습니다.
단점
- 초기 로드 크기: 스프라이트 파일이 크면 처음부터 필요 없는 아이콘까지 받을 수 있습니다.
- 스타일링: 인라인 SVG보다 내부 요소의 스타일을 바꾸기 어렵습니다.
SVG 자산을 관리하는 방법
SVG가 많아지면 같은 아이콘이 다른 이름으로 중복되거나, 최적화 수준과 색상 규칙이 파일마다 달라질 수 있습니다. 이름·저장 위치·내보내기 규칙을 정하고 빌드 과정에 최적화를 넣어야 디자인과 코드에서 같은 자산을 일관되게 사용할 수 있습니다.
1) 이름 규칙 정하기
아이콘이나 일러스트의 이름에서 종류와 상태를 알 수 있게 정합니다.
규칙 예시:
icon-[category]-[state].svg- e.g.
icon-user-active.svg,icon-alert-error.svg
- e.g.
- 같은 이름 패턴을 문서화해 디자인과 개발에서 함께 사용합니다.
2) 원본 위치 하나로 정하기
SVG의 원본 위치를 하나로 정해 같은 아이콘이 여러 폴더에서 따로 수정되지 않게 합니다.
Git 저장소에서는 아이콘을 기능이나 컴포넌트별 디렉터리로 나눌 수 있습니다.
/icons/
├── user/
│ ├── icon-user-active.svg
│ └── icon-user-inactive.svg
├── alert/
│ ├── icon-alert-error.svg
│ └── icon-alert-success.svg
정적 아이콘을 파일 URL로 제공한다면 S3와 CloudFront 같은 CDN에 배포할 수도 있습니다.
- 예: AWS S3 + CloudFront 또는 서비스형 CDN(Fastly, Akamai 등)
3) Storybook에서 확인하기
Storybook은 UI 컴포넌트 개발 도구로, SVG 아이콘 라이브러리를 문서화하고 시각적으로 확인할 수 있습니다.
- Storybook에 SVG를 컴포넌트로 등록해 크기, 색상, 변형 상태를 미리 봅니다.
- 컨트롤(Controls)로 SVG 속성을 바꾸며 확인합니다.
4) 내보내기 규칙 공유하기
디자인 단계에서 만든 SVG를 개발까지 문제없이 넘기려면 협업 절차를 표준화해야 합니다.
디자인 툴 활용:
- Figma나 Sketch에서 SVG를 내보낼 때 적용할 품질과 최적화 설정을 공유합니다.
- 예: 불필요한 그룹과 레이어 제거
- 디자인 시스템에 SVG 통합(예: Figma Tokens).
개발 단계 검증:
- 개발자는 SVG의 코드 구조와 최적화 결과를 확인합니다.
- 예: 중복된
<path>와 불필요한 인라인 스타일 확인
- 예: 중복된
같은 검증 기준을 디자인과 개발 단계에서 함께 적용하면 전달 과정에서 파일을 다시 손보는 일을 줄일 수 있습니다.
5) 빌드 과정에 자동화하기
빌드 과정에서 자동으로 최적화하면 SVG를 하나씩 손볼 일이 줄고, 모든 파일을 같은 기준으로 최적화할 수 있습니다.
- Webpack이나 Vite의 빌드 과정에 SVGO를 연결해 같은 설정으로 최적화합니다.
- 필요하면 스프라이트 생성기도 빌드 파이프라인에 추가합니다.

