Base64 images cost you caching

A data URI embeds a file directly in the document; the image travels inside the HTML or CSS rather than as a separate request. It was a standard optimisation for years, and the reasoning behind it has largely expired.

What it costs

Base64 encodes three bytes as four characters, so the encoded form is about 33% larger than the file. That is inherent to the encoding rather than an implementation detail: it uses 64 printable characters to represent arbitrary bytes, and 64 is 2⁶, so six bits go into each character that could have held eight. The data:image/png;base64, prefix adds a couple of dozen characters on top, which is nothing on a photograph and material on a 200-byte icon.

That 33% is worth being precise about, because it is routinely overstated. It is entirely real in the file on disk, in the parsed document and in memory. It is mostly not real on the wire. Text compression eats it: take 12 KB of already-compressed image data, which is what a JPEG or a WebP is, and the Base64 form is 16,000 characters, but gzipped it comes back to 12,070 bytes against 12,023 for the gzipped original. That is a transfer penalty of under half a per cent rather than a third. Compression cannot find patterns inside the image data, but it finds them easily in the 64-character alphabet laid over the top of it.

So the size argument is weaker than it is usually made to sound. The caching argument is not.

A separate image file is downloaded once, cached under its own URL, and reused on every page that references it and on every return visit. An embedded one is part of the document. It is re-downloaded with every page that carries it, re-decoded every time, and can never be cached independently of the text around it, so editing one word of the page invalidates the image too.

For a logo on one page that is nothing. For a logo on every page of a site it is strictly worse than a separate file, and the gap widens with the number of pages rather than with the size of the image.

What you give up besides the cache

The less obvious cost is that an inlined image stops being an image as far as the rest of the platform is concerned.

You lose Because
loading="lazy" it arrived with the document already
srcset and <picture> every variant would be another full copy inline
CDN resizing and format negotiation there is no URL left to intercept
An independent cache lifetime it expires when the document expires
A useful error a malformed URI simply does not render

There is a content security policy wrinkle too. A strict policy of img-src 'self' blocks data URIs outright, and allowing them means adding data: to the directive. That is a small but real widening — data: in img-src is generally considered acceptable, whereas data: in script-src very much is not, and the two are easy to relax together by habit.

Placement matters more than people expect as well. CSS is render-blocking: a stylesheet is downloaded and parsed in full before the browser paints anything at all. Half a megabyte of Base64 sitting in a stylesheet delays the first paint of the entire page, including every part of it that has nothing to do with the image.

Why the benefit shrank

Under HTTP/1.1 browsers opened about six connections per host and requests queued behind each other. Removing a request genuinely helped, and techniques like sprite sheets and inlining existed for that reason.

HTTP/2 and HTTP/3 multiplex many requests over one connection, so the cost of a second file is now small and the calculation that justified inlining mostly does not hold. The same era produced HTTP/2 Server Push, intended to remove the remaining round trip, and Chrome dropped support for it in 2022 on the grounds that it was harder to get right than it was worth. The pattern is consistent: the request stopped being the expensive part.

When it is still the right call

Situation Why
Small icon in a CSS file avoids a request for a few hundred bytes
HTML email external images are blocked by default
Single self-contained file no assets to ship alongside
Critical placeholder renders before anything else loads

A rough threshold from practice: under about 4 KB it is usually worth embedding, over about 10 KB it usually is not, and in between it depends on how often the page is loaded and whether the image repeats. Repetition decides it more often than size does. One 20 KB illustration on a landing page nobody revisits is fine; a 2 KB icon inlined into every page of a documentation site is not, and no threshold expressed in kilobytes will tell you that.

The SVG exception

For SVG specifically, skip Base64. An SVG is text, so percent-encoding it in CSS produces a smaller result than Base64 of the same file: percent-encoding only expands the characters that actually need escaping, while Base64 expands everything by a third. A 299-byte icon from this site is 400 characters as Base64 and 329 with the minimal escaping CSS requires.

It is also readable in the stylesheet afterwards, which occasionally matters, and it means changing a fill colour is an edit rather than a re-encode.

Questions people ask

Does encoding recompress the image? It should not. Encoding the original bytes gives a byte-identical image; going through a canvas would re-encode it.

Can I use data URIs in an img tag? Yes, and in CSS url(), and in an SVG image element.

Do they work in email? Mostly not; Gmail and Outlook strip or refuse them. That is the one case where the answer is an attachment.

Is there a size limit? Browsers accept several megabytes, but a data URI that large makes the containing file unparseable by hand and slow to process.

What if the deliverable is one file? Then embed everything, and the argument disappears. An offline report, a template, a page sent as an attachment; there is nothing to cache separately, so the only cost left is the third that compression mostly removes.

The image to Base64 tool encodes the original bytes and gives you the CSS or HTML form, base64 encode and base64 decode handle text, and the image compressor is the step worth taking before any of it, since a third of a smaller file is a smaller third.