RainyLab
Dev · Web

이미지를 파일로 둘까, 문자열로 심을까

이미지를 다루는 방법은 결국 둘 중 하나다. 파일로 두고 주소로 부르거나, 문자열로 바꿔 소스에 심거나. 파일은 요청이 생기는 대신 캐시되고, 문자열은 요청이 사라지는 대신 용량이 붙는다. 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 는 문서에 이미 실려 있다.

SVG 3개 · HTTP 요청 0회

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개다.

같은 path · stroke 만 다름

stroke="currentColor" 로 두면 글자 색을 그대로 따라간다. 다크 모드 대응이나 hover 색 변경까지 CSS 한 줄로 끝난다 — PNG 였다면 색깔 수만큼 파일을 만들어야 했다.

5. 파일이냐 문자열이냐 — 균형이 뒤집히는 곳

여기가 이 글의 본론이다. 같은 그림을 두고 파일로 둘 것인가 문자열로 심을 것인가는 취향이 아니라 크기와 개수로 갈린다.

파일로 둔다
  • 한 번 받으면 모든 페이지에서 캐시로 재사용된다
  • 같은 아이콘을 수십 곳에서 써도 바이트는 한 번
  • 대신 아이콘마다 요청이 하나씩 생긴다
  • 파일·경로·배포를 계속 관리해야 한다
문자열로 심는다
  • 요청 0회, 문서와 함께 도착한다
  • 관리할 파일이 아예 없다
  • 대신 33% 커지고, 페이지마다 다시 받는다
  • 같은 아이콘을 열 곳에 쓰면 열 번 실린다

그래서 기준은 바이트가 아니라 관리 비용이다. 1KB 아이콘의 33%는 330바이트라 없는 셈이고, 그 대신 파일 하나와 경로 하나가 사라진다 — 이 구간에서는 문자열이 이긴다. 아이콘이 수십 개로 늘거나 하나가 수십 KB가 되면 캐시를 포기한 값이 커지면서 균형이 다시 파일 쪽으로 넘어간다. 문자열화의 대가가 감당이 안 되는 더 큰 데이터에서 어떻게 되는지는 Blob URL 쪽 글에서 이어진다.

6. 문자열로 심기로 했다면 — base64냐 인라인 마크업이냐

base64 data URI
  • 요청 0회, 문서와 같이 도착한다
  • 한 줄이라 마크업이 지저분해지지 않는다
  • 색·상태가 고정인 아이콘에 맞다
  • CSS background-image 에 넣을 수 있다
인라인 SVG 마크업
  • 요청 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 보다 짧다.