Guide
Base64 adds a third to every file: when a data URI still wins
Base64 turns three bytes into four printable characters. That is a fixed 33% overhead before padding, and it is the price of putting binary data somewhere only text is allowed. The interesting question is not what it costs, but when the cost buys you something.
Where the 33% comes from
Base64 takes three bytes — 24 bits — and rewrites them as four characters of six bits each, drawn from an alphabet of 64 printable symbols. Four characters in place of three bytes is a ratio of 4/3, which is where the familiar 33% comes from.
Two details make the real number slightly worse. Padding rounds the output up to a multiple of four
characters, adding one or two = characters when the input length is not a multiple of three.
And some contexts wrap the output at 76 characters per line, adding a line break every 76 characters — a
further 2.6% or so.
| Original | Base64 | Increase |
|---|---|---|
| 1 KB | 1,368 bytes | +33.6% |
| 10 KB | 13,656 bytes | +33.4% |
| 100 KB | 136,532 bytes | +33.3% |
A data URI adds a fixed prefix on top — data:image/png;base64, is 22 characters — which is
negligible for anything but the smallest files.
The overhead is structural, not an implementation detail. No Base64 encoder is more efficient than another, because 4/3 is the ratio the encoding is defined by.
What gzip and Brotli give back
The common rebuttal is that compression cancels the overhead. It partly does, and the size of "partly" depends entirely on what was encoded.
Base64 output has less entropy per character than the bytes it came from: 64 possible values per character instead of 256, and a repeating four-character structure. Compressors exploit that, so compressed Base64 is usually well below 133% of the original. What it is rarely below is 100%.
The critical case is media that is already compressed. A PNG, a JPEG, a WebP or a ZIP is high-entropy by construction — that is what compressing it did. Base64-encode one and gzip the result and you land close to the original size, plus a few percent, having spent CPU on both ends to get back roughly where you started.
For text-like binary — an uncompressed BMP, a font with sparse tables, a small SVG — compression does more, because the underlying data was compressible in the first place. But then the honest comparison is against serving that file compressed and not Base64-encoded at all, which is smaller still.
The cost nobody measures: caching
Bytes are the visible cost. Cacheability is usually the larger one, and it does not appear in a file size comparison at all.
An image served as its own URL is a separate cache entry. The browser stores it, revalidates it independently, and reuses it on every page that references it. Change the HTML and the image is still cached. Change the image and the HTML is still cached.
Inline the same image as a data URI and it becomes part of whatever document contains it. It is fetched again with every uncached copy of that document, it cannot be reused across pages, and it cannot be revalidated on its own. Its bytes are now attached to the lifetime of the HTML or CSS around it.
That inversion is what makes inlining a poor default on a site people visit more than once. It is also why inlining into CSS is worse than it first looks: a stylesheet is usually cached aggressively and shared across every page, so padding it with image data enlarges the one file you most want to stay small and invalidates it whenever an image changes.
There is a render-blocking angle too. A data URI inside a stylesheet must be downloaded before the stylesheet is parsed, which means before first paint. A linked image is fetched in parallel and can be lazily loaded.
When inlining still wins
All of which makes it sound like data URIs are never worth it. They are, in a narrow and identifiable set of cases:
- Very small assets, above the fold. Below roughly one or two kilobytes, the request overhead — connection reuse, headers, round trip — can exceed the encoding overhead. Small icons and a placeholder image are the standard examples.
- Assets that must not be a separate request. A single-file HTML report, an email template, a document meant to work offline from a file system.
- Assets that change with the document. A generated chart or a QR code that is only ever valid for that one page has no independent cache lifetime to lose.
- Contexts where a URL is not available. An image in a JSON payload, a file uploaded through a text-only API, an attachment in a message body.
A rough test: if the same asset appears on a second page, it should have its own URL. If it exists only within one document and is small, inlining is reasonable.
Where Base64 is not optional
Most Base64 in practice is not an optimisation choice at all. It is there because a byte-hostile channel left no alternative:
- Email attachments. MIME bodies have been Base64 since the format was defined, because SMTP was specified for 7-bit text.
- JWTs and similar tokens. Base64url, with
-and_replacing+and/and padding dropped, so the value survives inside a URL. - Keys and certificates. PEM is Base64 with header and footer lines.
- Binary inside JSON or XML. Neither format has a byte type, so binary becomes a Base64 string.
- HTTP Basic authentication. Base64 of
user:password— encoding, not protection, which is the point that most needs repeating.
That last point generalises: Base64 is not encryption and provides no confidentiality. It makes bytes survive a text channel, nothing more.
A rule of thumb
- Assume +33%, plus a little for padding and line wrapping.
- Compression recovers some of it, and almost none of it for already-compressed media.
- The real cost is usually the lost cache entry, not the bytes.
- Inline only small, above-the-fold or single-document assets. Everything reused gets its own URL.
- When a channel only accepts text, Base64 is not a choice and the overhead is simply the price.
The Base64 Encoder reports the encoded size next to the original, so you can check the trade rather than estimate it.