이미지를 파일로 둘까, 문자열로 심을까
이미지를 다루는 방법은 결국 둘 중 하나다. 파일로 두고 주소로 부르거나, 문자열로 바꿔 소스에 심거나. 파일은 요청이 생기는 대신 캐시되고, 문자열은 요청이 사라지는 대신 용량이 붙는다. base64 data URI 는 문자열 쪽 선택지이고, 작은 아이콘에서는 그 균형이 문자열 쪽으로 기운다 — 파일을 만들지 않는 값이 늘어난 33% 보다 싸기 때문이다. 이 글은 그 균형이 어디서 뒤집히는지를 본다.
1. 기본 — img 의 src 에 그대로 넣는다
data: URI 는 파일 경로가 아니라 내용 자체를 적는 주소다.
브라우저는 이걸 보고 네트워크에 나가지 않고 문자열을 그 자리에서 이미지로 해석한다.
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0i…" alt="">
장식용 아이콘이면 alt="" 로 비워 두는 편이 낫다. 옆에 이미 글이 있는데
alt="체크" 를 달면 스크린 리더가 같은 말을 두 번 읽는다.
2. 여러 개를 써도 요청은 그대로 0회
아이콘이 늘어날수록 이점이 커진다. 파일 방식이라면 아이콘 세 개는 요청 세 번이고, HTTP/2 라 해도 각각 왕복이 필요하다. data URI 는 문서에 이미 실려 있다.
3. CSS 배경으로도 같은 문자열을 쓴다
background-image 에 넣으면 마크업을 건드리지 않고 아이콘을 붙일 수 있다.
버튼처럼 아이콘이 장식에 가까운 자리에 어울린다.
.icon-download {
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0i…");
background-size: contain;
background-repeat: no-repeat;
}
4. 색을 바꿔야 한다면 base64 를 쓰지 않는다
여기가 가장 자주 헷갈리는 지점이다. SVG 는 CSS 로 색을 바꿀 수 있지만,
그건 마크업이 문서에 펼쳐져 있을 때 이야기다. base64 로 감싸 img 에 넣는 순간
브라우저는 그것을 하나의 이미지로 취급하고, 바깥의 CSS 는 안쪽 stroke 에 닿지 않는다.
아래 세 개는 색이 다르지만 base64 가 아니라 인라인 SVG다. 같은 path 하나에
stroke 만 다르게 준 것이고, 파일은 여전히 0개다.
stroke="currentColor" 로 두면 글자 색을 그대로 따라간다. 다크 모드 대응이나
hover 색 변경까지 CSS 한 줄로 끝난다 — PNG 였다면 색깔 수만큼 파일을 만들어야 했다.
5. 파일이냐 문자열이냐 — 균형이 뒤집히는 곳
여기가 이 글의 본론이다. 같은 그림을 두고 파일로 둘 것인가 문자열로 심을 것인가는 취향이 아니라 크기와 개수로 갈린다.
- 한 번 받으면 모든 페이지에서 캐시로 재사용된다
- 같은 아이콘을 수십 곳에서 써도 바이트는 한 번
- 대신 아이콘마다 요청이 하나씩 생긴다
- 파일·경로·배포를 계속 관리해야 한다
- 요청 0회, 문서와 함께 도착한다
- 관리할 파일이 아예 없다
- 대신 33% 커지고, 페이지마다 다시 받는다
- 같은 아이콘을 열 곳에 쓰면 열 번 실린다
그래서 기준은 바이트가 아니라 관리 비용이다. 1KB 아이콘의 33%는 330바이트라 없는 셈이고, 그 대신 파일 하나와 경로 하나가 사라진다 — 이 구간에서는 문자열이 이긴다. 아이콘이 수십 개로 늘거나 하나가 수십 KB가 되면 캐시를 포기한 값이 커지면서 균형이 다시 파일 쪽으로 넘어간다. 문자열화의 대가가 감당이 안 되는 더 큰 데이터에서 어떻게 되는지는 Blob URL 쪽 글에서 이어진다.
6. 문자열로 심기로 했다면 — base64냐 인라인 마크업이냐
- 요청 0회, 문서와 같이 도착한다
- 한 줄이라 마크업이 지저분해지지 않는다
- 색·상태가 고정인 아이콘에 맞다
- CSS
background-image에 넣을 수 있다
- 요청 0회는 같다
currentColor·hover·다크 모드 대응- 부분마다 다른 색, 애니메이션 가능
- 대신 마크업이 길어진다
둘은 문자열이라는 점이 같고, 둘 다 벡터라 배율 대응 파일(2x, 3x)이 필요 없다. 아이콘을 PNG/GIF 파일로 두는 선택지는 이 지점에서 거의 밀려난다 — 사진이 아닌 이상, 파일로 남길 이유는 캐시 하나뿐이다.
중간 지대: 스프라이트
아이콘이 수십 개라 문자열이 부담스럽고, 그렇다고 파일을 수십 개 두기도 싫다면
가운데가 있다. SVG 스프라이트(<symbol> + <use>)는
파일 하나에 아이콘 전부를 담는다. 요청은 1회로 묶이고 캐시도 그대로 받으며,
<use> 로 꺼낸 도형은 currentColor 도 따른다.
문자열의 이점 대부분을 파일 쪽에서 되찾는 방법이다.
7. 인코딩하는 법
브라우저 콘솔에서 한 줄이면 된다.
btoa('<svg xmlns="http://www.w3.org/2000/svg" …></svg>')
Node 라면:
Buffer.from(svg, 'utf8').toString('base64')
btoa() 는 Latin-1 범위만 받는다. SVG 안에 한글 <text> 가 들어 있으면
그대로 던졌을 때 InvalidCharacterError 가 난다. 이럴 때는 base64 를 건너뛰고
URL 인코딩 방식(data:image/svg+xml,%3Csvg…)을 쓰는 게 간단하다.
이 사이트의 파비콘이 그 방식이고, 덤으로 base64 보다 짧다.