[가이드] 데이터 URI 이해하기: data:image/png;base64,... 완벽 해설
data: URI 스킴에 대한 실용 가이드입니다. data:image/png;base64,iVBORw0KGgo...의 모든 구성 요소가 실제로 무엇을 의미하는지, 인라인이 적절한 선택인 경우는 언제인지, 그리고 언제 역효과가 나는지를 설명합니다.
// data: URI는 정확히 무엇인가
data: URI는 https:나 file:과 같은 URI 스킴의 한 종류로, 원격 위치를 가리키는 대신 리소스 자체를 URI 문자열 안에 직접 내장합니다. RFC 2397에 정의되어 있으며, 작은 바이너리 또는 텍스트 데이터 덩어리를 마치 URL처럼 다룰 수 있게 해줍니다.
실제로 가장 흔히 보게 되는 형태는 이미지용입니다: data:image/png;base64,iVBORw0KGgoAAAANSUhEUg.... 브라우저는 이 URL을 보고 MIME 타입을 읽은 뒤 Base64 페이로드를 다시 바이트로 디코딩하여 이미지를 렌더링합니다. HTTP 요청은 단 한 번도 발생하지 않습니다.
// 문법을 한 조각씩 살펴보기
전체 문법은 data:[<mediatype>][;base64],<data>입니다. 선택적인 세 부분과 필수 구분자(쉼표) 하나로 이루어집니다. 실제 예제를 분해해 보겠습니다.
data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...를 왼쪽에서 오른쪽으로 읽어봅시다.
• data: — 스킴입니다. 파서에게 "리소스가 URL 자체에 들어있으니 별도의 페치(fetch)가 필요 없다"고 알려줍니다.
• image/png — MIME 타입입니다. 페이로드를 어떻게 해석할지 렌더러에 알려줍니다. 생략하면 기본값은 text/plain;charset=US-ASCII입니다.
• ;base64 — 인코딩 플래그입니다. 페이로드가 Base64(RFC 4648)임을 명시합니다. 이것이 없으면 페이로드는 URL 인코딩된 텍스트로 해석됩니다.
• , — 메타데이터와 페이로드 사이의 필수 구분자입니다.
• iVBORw0KGgo... — 실제 Base64로 인코딩된 이미지 바이트입니다.
// Anatomy diagram
// data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...
// │ │ │ │
// │ │ │ └── Base64 payload (the actual image bytes)
// │ │ └────────── encoding flag (always 'base64' for binary)
// │ └──────────────────── MIME type (optional, defaults to text/plain)
// └───────────────────────── scheme literal (always 'data:')
// data: URI가 존재하는 이유
사람들이 일반 이미지 URL 대신 데이터 URI를 선택하는 데에는 세 가지 이유가 있습니다.
1. HTTP 요청을 제거한다. 페이지의 모든 <img src="...">는 네트워크 왕복 비용을 발생시킵니다. 작은 아이콘이나 스프라이트의 경우, 그 왕복이 실제 바이트 전송보다 더 오래 걸릴 수 있습니다. 이미지를 데이터 URI로 인라인하면 HTML/CSS와 함께 묶이게 되어 요청 자체가 사라집니다. 이 점은 HTTP/1.1 시대에는 매우 중요했고, HTTP/2의 멀티플렉싱이 도입된 지금은 비중이 줄었지만, 위쪽 폴드(above-the-fold)의 핵심 아이콘에는 여전히 도움이 됩니다.
2. 자산을 자기 완결적으로 만든다. HTML 이메일은 원격 이미지를 기본 차단하는 클라이언트(Gmail, Outlook)에서도 렌더링되어야 합니다. 클라이언트에게 이메일로 보내는 정적 보고서는 오프라인 상태에서도 회사 로고가 표시되어야 합니다. 북마클릿은 복사-붙여넣기에도 살아남는 아이콘이 필요합니다. 문서 스니펫은 마크다운과 함께 이동할 수 있는 스크린샷이 필요합니다. 이 모든 경우에 이미지는 문서 안에 있어야 하며, 데이터 URI가 그것을 가능하게 해줍니다.
3. 프로그래밍적 생성. 코드가 런타임에 이미지를 생성하는 경우 — QR 코드, 차트, 서명 패드 — 서버에 먼저 업로드하여 URL을 받지 않고도 표시해야 합니다. canvas.toDataURL('image/png')는 데이터 URI를 즉시 반환해 주며, 이를 img.src에 할당하는 것은 가능한 가장 단순한 워크플로입니다.
// 실제로 보게 될 모든 MIME 타입
MIME 타입 슬롯은 어떤 미디어 타입이든 받아들이지만, 실제로 이미지에서 보게 되는 것은 소수입니다.
• image/png — 가장 흔합니다. 무손실이며 투명도를 지원합니다. Base64는 iVBORw0KGgo로 시작합니다.
• image/jpeg — 사진과 스크린샷용입니다. 손실 압축입니다. Base64는 /9j/로 시작합니다.
• image/gif — 레거시 및 애니메이션 이미지입니다. Base64는 R0lGOD로 시작합니다.
• image/webp — 최신, 더 작은 파일 크기입니다. Base64는 UklGR로 시작합니다.
• image/svg+xml — 벡터 그래픽입니다. SVG는 텍스트이므로 Base64로 인코딩하거나(;base64 포함) URL 인코딩할 수 있습니다(생략). URL 인코딩된 SVG가 보통 더 작습니다.
• image/x-icon 또는 image/vnd.microsoft.icon — 파비콘입니다. Base64는 AAABAA로 시작합니다.
<!-- All five types in a single HTML document -->
<img src="data:image/png;base64,iVBORw0KGgo..." alt="PNG inline">
<img src="data:image/jpeg;base64,/9j/4AAQSkZJRg..." alt="JPEG inline">
<img src="data:image/gif;base64,R0lGODlhAQABAA..." alt="GIF inline">
<img src="data:image/webp;base64,UklGRiIAAABXRU..." alt="WebP inline">
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxu..." alt="SVG inline">
// HTML, CSS, JavaScript에서 data: URI 사용하기
URL이 허용되는 모든 곳에서 데이터 URI는 동일한 방식으로 동작합니다.
<!-- HTML <img> tag -->
<img src="data:image/png;base64,iVBORw0KGgo..." width="32" height="32" alt="icon">
<!-- HTML <link> for favicon -->
<link rel="icon" href="data:image/x-icon;base64,AAABAAEAEBA...">
/* CSS background-image */
.button-icon {
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxu...');
background-size: 16px 16px;
}
/* CSS @font-face — yes, fonts work too */
@font-face {
font-family: 'InlineFont';
src: url('data:font/woff2;base64,d09GMgAB...') format('woff2');
}
// JavaScript — assign directly to img.src
const img = new Image();
img.src = 'data:image/png;base64,iVBORw0KGgo...';
document.body.appendChild(img);
// JavaScript — fetch a data URI like any other URL
const response = await fetch('data:application/json;base64,eyJrZXkiOiJ2YWx1ZSJ9');
const data = await response.json(); // { key: 'value' }
// CANVAS, FILE, BLOB → data: URI
다음 세 가지 브라우저 API는 직접 Base64 작업을 하지 않아도 데이터 URI를 반환해 줍니다.
// 1. Canvas → data URI (instant)
const canvas = document.querySelector('canvas');
const pngDataURI = canvas.toDataURL('image/png');
const jpegDataURI = canvas.toDataURL('image/jpeg', 0.85); // quality 0–1
// pngDataURI === 'data:image/png;base64,iVBORw0KGgo...'
// 2. <input type="file"> → data URI via FileReader
const file = document.querySelector('input[type=file]').files[0];
const reader = new FileReader();
reader.onload = () => {
// reader.result is the data URI
document.querySelector('img').src = reader.result;
};
reader.readAsDataURL(file);
// 3. Blob → data URI (manual, but rarely needed — use URL.createObjectURL instead)
const blob = new Blob([bytes], { type: 'image/png' });
const reader2 = new FileReader();
reader2.onload = () => console.log(reader2.result);
reader2.readAsDataURL(blob);
// Or, for in-page display, prefer:
const objectURL = URL.createObjectURL(blob); // 'blob:https://...' — much shorter, no Base64
// 33% 크기 패널티 (그리고 그것이 중요할 때)
Base64는 바이너리 3바이트를 ASCII 4문자로 변환하므로, 인코딩된 페이로드는 정확히 4⌈n/3⌉ 바이트가 됩니다. 원본보다 대략 33% 더 큽니다. 12 KB짜리 PNG는 약 16 KB의 데이터 URI가 되며, 여기에 data:image/png;base64, 접두사를 위한 몇 바이트의 오버헤드가 추가됩니다.
이 점은 언제 중요할까요?
• 아주 작은 자산(4 KB 미만): 33%의 부풀림은 절약된 HTTP 왕복에 비하면 미미합니다. 자유롭게 인라인하세요.
• 중간 크기 자산(4–50 KB): 사례별 판단이 필요합니다. HTTP/2와 캐싱이 있다면 별도 요청이 매 페이지 로드마다 인라인된 버전을 다시 다운로드하는 것보다 보통 더 빠릅니다.
• 큰 자산(50 KB 이상): 거의 절대로 인라인하지 마세요. 데이터 URI는 그것을 포함하는 모든 캐시된 HTML 페이지, 모든 이메일, 모든 JSON 페이로드를 부풀립니다. 별도의 URL을 사용하세요.
데이터 URI에는 캐싱이 없습니다. 브라우저는 바이트가 HTML에 박혀있는 이미지를 캐싱할 수 없습니다. 동일한 이미지가 여러 페이지에 나타나면 모든 페이지가 전체 비용을 지불합니다. HTTP 캐시 헤더를 가진 일반적인 <img src="/logo.png">는 한 번만 다운로드되고 모든 곳에서 재사용됩니다.
// 마주치게 될 브라우저 한계
브라우저는 데이터 URI에 단일한 하드 리미트를 부과하지 않지만, 실제 환경에서의 상한은 존재합니다.
• Chrome / Edge / Firefox: <img src>의 데이터 URI는 수 메가바이트까지 작동합니다. 약 32 MB를 넘으면 탭당 메모리 압박을 겪게 됩니다.
• Safari: 역사적으로 <a href>(다운로드 링크)의 데이터 URI를 약 2 MB로 제한했습니다. <img>의 경우 더 큰 크기도 동작하지만 렌더링이 느려집니다.
• Internet Explorer 8: CSS와 HTML의 데이터 URI에 대해 최대 32 KB. (IE9+에서 한계가 제거되었으나, 결코 최신 브라우저만큼 빠르지 않았습니다.)
• HTTP 요청 라인: <form action>이나 쿼리 문자열의 데이터 URI는 브라우저가 허용하는 URL 길이에 의해 제한됩니다(IE는 약 2 KB, 그 외에는 8 KB 이상).
이러한 한계 중 어느 하나에 가까워지고 있다면, 그 자산은 인라인하기엔 너무 큽니다. 일반 URL이나 URL.createObjectURL(blob)로 전환하세요. 후자는 메모리에 보관 가능한 blob에 의해 뒷받침되는 짧은 blob: URL을 제공합니다.
// 보안 우려사항
데이터 URI는 네트워크가 필요 없어 편리하지만, 콘텐츠가 서버에서 온다는 가정에 기반한 여러 웹 보안 메커니즘을 우회합니다.
동일 출처 정책(Same-origin policy). data: URI는 기술적으로 opaque origin(불투명 출처)으로 간주됩니다(최신 Chrome/Firefox 기준). 따라서 데이터 URI에서 로드된 JavaScript는 부모 문서의 쿠키, localStorage, DOM에 제한 없이 접근할 수 없습니다. 신뢰할 수 없는 콘텐츠를 샌드박싱하는 데는 좋지만, 출처 검사에 의존하는 트래킹, 분석, CSRF 보호가 오작동할 수 있다는 의미이기도 합니다.
data:text/html을 통한 XSS. 사용자가 실행한 data:text/html,<script>...</script> URL은 완전한 권한을 가진 페이지가 됩니다. 이러한 이유로 최신 브라우저들은 data:text/html로의 최상위 탐색을 차단합니다(Firefox는 2018년부터, Chrome은 2020년부터). 사용자 제공 URL을 data: 스킴을 허용하는 어떤 리다이렉트로도 통과시키지 마세요.
콘텐츠 보안 정책(CSP). 기본적으로 엄격한 CSP는 img-src, style-src 등에서 데이터 URI를 차단합니다. 이를 허용하려면 디렉티브에 data:를 명시적으로 포함시켜야 합니다(예: img-src 'self' data:). 신중하게 결정하세요. 데이터 URI를 한번 허용하면 페이지가 인라인하기로 선택한 어떤 이미지(또는 폰트)든 받아들이게 됩니다.
로깅과 PII. 데이터 URI는 URL의 일부입니다. URL을 캡처하는 모든 로거나 분석 도구(서버 액세스 로그, 브라우저 히스토리, 에러 트래커)는 페이로드 전체를 캡처하게 됩니다. 데이터 URI에 사용자의 아바타, 사진, 또는 스크린샷이 포함되어 있다면 그대로 로그에 남게 됩니다. 통제할 수 없는 URL에는 PII를 넣지 마세요.
// data: URI vs blob: URL — 무엇을 사용해야 하는가?
둘 다 업로드 없이 메모리 내 바이너리 데이터를 표시할 수 있게 해줍니다. 선택은 데이터가 어디에 있어야 하는지에 달려 있습니다.
data: URI는 데이터가 이식 가능해야 할 때 사용하세요. HTML에 내장되거나, JSON으로 전송되거나, 데이터베이스에 저장되거나, 이메일에 복사-붙여넣기되어야 하는 경우입니다. 데이터가 곧 URL입니다.
blob: URL은 데이터가 로컬에 있고 수명이 짧을 때 사용하세요. SPA의 미리보기, 다운로드 트리거, 페이지가 닫히면 사라지는 이미지 등입니다. URL.createObjectURL(blob)은 짧은 식별자(blob:https://example.com/12345-abcde)를 반환해 주며, 브라우저는 이를 메모리 내 blob으로 다시 매핑합니다. 데이터 URI보다 훨씬 작고, 사용을 마쳤을 때 URL.revokeObjectURL(url)로 해제할 수 있습니다.
원칙: URL이 현재 페이지를 떠나야 한다면 data:를 사용하세요. 페이지 안에 머문다면 blob:을 사용하세요.
// 흔한 디버깅 이슈
DevTools에서 잘린 문자열. 네트워크 탭이나 엘리먼트 패널에서 긴 데이터 URI를 복사할 때, 브라우저는 종종 값을 줄임표로 잘라냅니다. 표시된 텍스트는 전체 URL이 아닙니다. 전체 문자열을 얻으려면 소스 파일(CSS, HTML, JSON)을 직접 열거나 콘솔에서 document.querySelector('img').src를 사용하세요. 그러면 완전한 URL이 반환됩니다.
잘못된 MIME 타입. data:image/jpeg;base64,...로 라벨된 PNG 페이로드도 바이트 수준에서는 여전히 디코딩됩니다. Base64는 MIME에 신경 쓰지 않기 때문입니다. 하지만 일부 뷰어는 불일치를 거부합니다. 실제 형식은 매직 바이트(PNG의 경우 iVBORw0KGgo)에 의해 결정되므로, 의심스럽다면 페이로드를 Base64 to Image 디코더에 붙여넣어 보세요. 바이트로부터 자동 감지합니다.
쉼표 누락. 형식은 data:image/png;base64,iVBORw...이지 data:image/png;base64;iVBORw...가 아닙니다. 세미콜론이나 구분자 누락은 전체 URL을 무효화합니다.
data: URI 안의 URL-안전 Base64. 표준 data: URI는 표준 Base64 알파벳(+ 와 / 사용)을 사용합니다. 페이로드가 URL-안전 변형(RFC 4648 §5, - 와 _ 사용)으로 인코딩되었다면 내장하기 전에 다시 변환해야 합니다. Base64 URL-안전 vs 표준 가이드를 참고하세요.
페이로드 내부의 공백. 일부 프리티 프린터는 긴 Base64 문자열을 76 컬럼마다 줄바꿈으로 감쌉니다. 대부분의 브라우저는 <img src>에서 이를 허용하지만, 일부(특히 오래된 WebView)는 그렇지 않습니다. 내장 전에 공백을 제거하세요: str.replace(/\s+/g, '').
// data: URI를 사용하지 말아야 할 때
• 50 KB 초과. 캐시 무효화 + 다운로드 비용이 절약되는 요청을 능가합니다.
• 반복되는 이미지. 두 페이지 이상에서 사용되는 모든 것은 캐시 헤더를 가진 일반 URL이어야 합니다.
• 외부 공개 분석. 추적 픽셀, 비콘, 픽셀 기반 어트리뷰션은 로그할 HTTP 요청이 없기 때문에 작동하지 않습니다.
• SPA에서 표시되는 사용자 업로드 콘텐츠. 대신 URL.createObjectURL(blob)을 사용하세요. Base64 작업을 완전히 회피하며 메모리상 약 4배 더 작습니다.
• data:를 허용하지 않는 엄격한 CSP 환경 내부. 데이터 URI를 사용하기 위해서만 CSP에 data:를 추가하지 마세요. 정말 필요한지 먼저 자문해 보세요.
// 30초 치트 시트
형식: data:[mime-type][;base64],[payload]
가장 흔한 형태: data:image/png;base64,iVBORw0KGgo...
이미지로 다시 디코딩: Base64 to Image 변환기에 붙여넣으세요.
이미지를 데이터 URI로 인코딩: Image to Base64 변환기를 사용하세요. 붙여넣을 준비가 된 완전한 data: URI를 생성합니다.
인라인 예산: 4 KB 미만은 항상 인라인, 4–50 KB는 상황에 따라, 50 KB 초과는 일반 URL 사용.
브라우저 API: canvas.toDataURL(), FileReader.readAsDataURL(), 또는 직접 문자열을 구성: 'data:image/png;base64,' + btoa(binaryString).
관련 읽을거리: Base64 이미지 임베딩 · 브라우저에서 Base64 이미지 미리보기 · Base64 vs Base64URL vs Base32