How to Embed Images as Base64 Data URIs (and When You Shouldn't)
There's a moment every web developer hits where they're staring at a tiny icon — maybe a 400-byte checkmark SVG or a small loading spinner — and thinking: do I really need a separate HTTP request for this thing? That's usually when someone mentions Base64 data URIs, and either solves your problem or creates three new ones depending on how you use them.
This guide walks through exactly how to inline images using Base64 encoding, shows you the real trade-offs that most tutorials gloss over, and helps you make an honest call about whether it actually helps in your specific situation.
What a Base64 Data URI Actually Is
When a browser loads an image normally, it makes an HTTP request to a URL like https://example.com/logo.png. A data URI sidesteps that entirely by embedding the raw file contents directly into the HTML or CSS as a text string.
The format looks like this:
data:[mediatype];base64,[encoded-data]
For a PNG image, that becomes:
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...
The iVBORw0KGgo... part is your original binary image file run through Base64 encoding — a process that converts arbitrary binary data into a string of 64 printable ASCII characters. This is necessary because HTML and CSS are text formats; you can't just paste raw binary bytes into them.
The trade-off baked into Base64 encoding is that it inflates file size by roughly 33%. A 10KB PNG becomes approximately 13.3KB as Base64. That overhead matters, and we'll come back to it.
How to Convert an Image to Base64
There are several ways to do this depending on your workflow.
On the Command Line
If you're on macOS or Linux, base64 is built in:
base64 -i yourimage.png | tr -d '\n'
The tr -d '\n' strips the line breaks that the base64 tool inserts every 76 characters — you want the whole thing as one continuous string for embedding.
On Windows with PowerShell:
[Convert]::ToBase64String([IO.File]::ReadAllBytes("yourimage.png"))
With a Web-Based Converter
If you're doing this occasionally and don't want to touch a terminal, a Base64 image converter tool works fine. You upload the file, it spits out the encoded string, and you copy-paste it into your code. Just make sure you're using a tool that doesn't store uploaded files on their server if your image is proprietary.
In Node.js
const fs = require('fs');
const data = fs.readFileSync('yourimage.png');
const b64 = data.toString('base64');
const dataUri = `data:image/png;base64,${b64}`;
console.log(dataUri);
This is useful if you're automating the process as part of a build step.
Using Data URIs in HTML
Once you have the encoded string, drop it straight into an img tag's src attribute:
<img
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA..."
alt="Small checkmark icon"
width="16"
height="16"
>
That's it. The browser treats it identically to an external image URL — it decodes the Base64, reconstructs the binary image data, and renders it. No additional network request happens.
Using Data URIs in CSS
CSS background images work the same way. Instead of:
.icon-check {
background-image: url('/assets/icons/check.png');
}
You write:
.icon-check {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAA...');
}
This is particularly common for small decorative elements that you want tightly coupled to a stylesheet — like custom radio button states or tiny UI indicators.
For SVGs, you actually have two options. You can Base64-encode the SVG like any other file, or you can use URL-encoded SVG directly, which often produces shorter strings for simple graphics:
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10'%3E%3Ccircle cx='5' cy='5' r='4'/%3E%3C/svg%3E");
The URL-encoded approach skips the 33% size overhead of Base64, making it genuinely leaner for SVGs. Worth knowing.
The Real Performance Math
Here's where most tutorials either stop or get cheerleader-y about data URIs. Let's be more honest.
Where Data URIs Actually Help
Reducing HTTP/1.1 request overhead. Under the old HTTP/1.1 protocol, each resource request has real latency cost — TCP handshakes, queue time, headers. If you have 20 tiny icons, that's 20 round trips. Inlining them eliminates those round trips. On HTTP/1.1-only servers, this used to be a legitimate optimization technique.
Self-contained single-file deliverables. HTML email is a classic example. Emails often can't reliably reference external images (blocked by email clients, or the images disappear when the server goes away). Embedding a company logo as Base64 in the email HTML ensures it always shows up.
Critical above-the-fold CSS. If you're inlining your critical CSS in a <style> tag in the document head, any background images referenced by that CSS would still trigger external requests. Embedding them as data URIs means your entire above-the-fold render can happen from a single HTML document.
Where Data URIs Hurt
HTTP/2 makes the request-count argument mostly irrelevant. HTTP/2 multiplexes multiple requests over a single connection. Parallelism is built in. The "save a request" justification that made data URIs popular under HTTP/1.1 is largely gone for modern sites. Most servers today support HTTP/2.
Data URIs don't get cached independently. This is the big one that bites people. When you reference /assets/logo.png normally, the browser caches that file. Every page that uses it benefits from the cache. When you embed it as Base64 in your HTML or CSS, the image data lives inside that document. If the page changes even slightly, the whole thing re-downloads — including the image data. Your logo now re-downloads on every page load on every page of your site, instead of being cached once per user session.
Bloated HTML means slower initial parse. A 50KB inline Base64 image added to every page's HTML increases the parse time for every single document. The browser has to process that string before it can render anything.
They're invisible to some image optimizers and service workers. External images can be served via a CDN with aggressive caching headers, optimized on-the-fly, converted to WebP automatically, or cached by a service worker for offline use. Data URIs sit opaquely inside your markup and skip all of that.
The Honest Decision Framework
Given all that, here's a practical way to decide:
Use Base64 data URIs when:
- The image is tiny (under 2KB as a raw file, so under ~2.7KB encoded)
- It's unique to a single page or component and won't be reused across your site
- You're generating an HTML email or a self-contained offline document
- You're embedding a small SVG icon in inlined critical CSS
- You're targeting an environment where external requests are restricted
Skip Base64 data URIs when:
- The image appears on multiple pages (caching benefit of a standalone file far outweighs anything else)
- The image is over about 5KB — the encoding overhead and caching loss aren't worth it
- Your server already speaks HTTP/2
- You care about service worker caching or CDN edge delivery for images
- You're dealing with photographs or large graphics of any kind
A Note on Security
Data URIs are generally safe for images, but they've historically been abused in other contexts (embedding JavaScript as data:text/html for phishing, for instance). Modern browsers restrict a lot of that, but it's worth knowing that some Content Security Policies explicitly block data URIs, and if you're building something that accepts user-uploaded HTML, be careful about letting users inject data URIs of arbitrary types.
For your own static assets as images, there's no meaningful security concern — you're just encoding the same data that would have come over the wire anyway.
Putting It Together
Data URIs are a tool with a narrow ideal use case, not a general-purpose optimization. They made more sense in 2010 when HTTP/1.1 dominated and reducing request count was a primary concern. In 2024, the calculation is different: HTTP/2 is ubiquitous, browser caching is sophisticated, and CDN delivery for images is cheap.
Where they still shine is in self-contained documents — HTML emails, single-file HTML widgets, offline apps, or very small icons tightly coupled to inlined CSS. Outside those cases, a standard <img src="..."> pointing to a properly cached external file will almost always outperform the inline approach over real user sessions.
The next time you're tempted to inline that hero image as Base64 "for performance," run the math first: what's the raw size, does it appear on multiple pages, and is caching that file separately going to save more bytes over time than the request overhead costs? Usually it will.