Image to Base64: when data URIs help and when they hurt
· 6 min read
Inlining an image as Base64 is one of those tricks that is either a small win or a quiet performance problem, depending on the image. A 300-byte icon inlined into CSS saves a request. A 2 MB hero photo inlined into HTML delays the whole page and can never be cached on its own.
This article covers how data URIs work, the real costs, the rules bundlers use to decide what to inline, and a few cases (SVG, email) where the usual advice doesn’t apply. The image to Base64 converter shows the sizes for your own files and produces ready-to-paste snippets.
What a data URI is
A data URI puts the file’s content directly into the URL (RFC 2397):
data:[<media type>][;base64],<data>
For a PNG that looks like:
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB..." alt="" />
The same string works anywhere a URL does: src in HTML, url() in CSS, or an image link in Markdown:
.icon {
background-image: url('data:image/png;base64,iVBORw0KGgo...');
}

The converter gives you all of these formats for an uploaded image, plus the raw Base64.
What it costs
Size
Base64 turns every 3 bytes into 4 characters, so the encoded image is about 33% larger than the file. Gzip or Brotli on the HTML or CSS wins back part of that, because Base64 text compresses somewhat, but not all of it. Compressed formats such as PNG, JPEG and WebP are already dense, so there isn’t much left to squeeze.
Caching
An image file referenced by URL is cached on its own. When the same logo appears on every page, it’s downloaded once. When it’s inlined, it’s downloaded again inside every HTML page, or inside the CSS file, and cached only as part of that file. Change one line of CSS and every inlined image is downloaded again.
Rendering
Images referenced by URL load in parallel with the rest of the page and don’t block rendering. Base64 inside the HTML or a render-blocking stylesheet has to be downloaded and parsed before the page can show anything. A big inlined image makes the page appear later, not sooner.
When inlining wins
The saving is one HTTP request. With HTTP/1.1, where browsers opened a handful of connections per host, requests were expensive and inlining small images was standard advice. With HTTP/2 and HTTP/3, many requests share one connection, and the saving is much smaller. Inlining still makes sense for:
- tiny images (a few hundred bytes to a few kilobytes), such as icons, 1×1 spacers and small placeholders
- images needed for the very first paint, when the request would otherwise start late
- self-contained documents: a single HTML report, an email template preview, or a test fixture that must not depend on external files
How bundlers decide
Build tools inline small assets automatically and link to larger ones. The defaults are a good sanity check for your own choices:
| Tool | Setting | Default limit |
|---|---|---|
| Vite | build.assetsInlineLimit |
4 KiB |
webpack 5 (asset type) |
parser.dataUrlCondition.maxSize |
8 KiB |
If a file is below the limit it becomes a data URI; above it, a separate file with a hashed name. So when you’re inlining by hand, a few kilobytes is a reasonable ceiling. For anything larger, a normal file is usually faster.
SVG: skip Base64 entirely
SVG is text, so it doesn’t need Base64 at all. A URL-encoded SVG data URI is usually smaller than the Base64 version and still readable:
/* Base64 */
background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciLz4=');
/* URL-encoded */
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'/%3E");
The encoded form needs <, >, # and " escaped (or ' used for attributes inside a double-quoted url("...")). And for icons used in the HTML itself, inline <svg> markup is often better than either option: it can be styled with CSS and inherit currentColor.
Email is different
Email is the one place where people most want inline images and where data URIs work worst. Gmail and several versions of Outlook don’t display data: images in HTML email. The standard alternatives:
- CID attachments: attach the image to the message and reference it with
src="cid:logo". - Hosted images: link to an image on your server (the approach most newsletter tools use).
Base64 is still involved in both cases, because MIME encodes the attachment in Base64. The mail client handles it, though, not your HTML.
Checking what you actually have
A data URI declares its own type, and the declaration can be wrong. data:image/png;base64,/9j/4AAQ... claims PNG, but the bytes /9j/ decode to FF D8 FF, which is a JPEG signature. Browsers sniff the real type and usually still show it, but other consumers may not.
Image formats identify themselves by their first bytes:
| Format | First bytes (hex) | Base64 usually starts with |
|---|---|---|
| PNG | 89 50 4E 47 0D 0A 1A 0A |
iVBORw0KGgo |
| JPEG | FF D8 FF |
/9j/ |
| GIF | 47 49 46 38 (GIF8) |
R0lGOD |
| WebP | 52 49 46 46 … 57 45 42 50 |
UklGR |
The Base64 to image decoder reads these signatures, shows the real type, the pixel dimensions and the size, and warns when the declared type doesn’t match. It also accepts Base64 without the data: prefix, with line breaks, or in the URL-safe alphabet, which is handy when the string comes from a log or an API response.
A short decision guide
- Is the image smaller than about 4 KB and used on only a few pages? Inline it.
- Is it SVG? Use inline
<svg>markup or a URL-encoded data URI, not Base64. - Is it a photo or anything over a few kilobytes? Serve it as a file, with a proper cache header.
- Is it for an email? Use a CID attachment or a hosted image.
To see the numbers for a specific image, drop it into the image to Base64 converter. It shows the file size and the data URI size side by side. For plain text or other binary data, use the general Base64 encoder.