Inlining an asset as a data: URI trades a network request for bytes in your CSS or HTML. Sometimes that is a clear win and sometimes it silently makes every page load worse. The deciding factors are size, reuse and cacheability — and they are easy to reason about once you know the numbers.
Base64 encodes 3 bytes into 4 ASCII characters, so every inlined asset grows by roughly 33 percent before compression. Brotli and gzip claw some of that back on text-like payloads (an SVG inlines and compresses reasonably), but binary formats such as PNG, WOFF2 and JPEG are already compressed, so base64 of a WOFF2 file compresses almost not at all. A 40 KB font becomes about 54 KB of near-incompressible text sitting in your critical CSS.
The pattern that consistently pays off is small, render-critical, single-use assets. Build tools have converged on a threshold around 4 KB — Vite's build.assetsInlineLimit defaults to 4096 bytes, and webpack's asset modules use 8 KB. Below that, the request overhead genuinely outweighs the 33 percent tax. Above it, ship a file.
The other good case is anything needed to avoid a visual flash: a 1×1 background gradient stop, a tiny logo mark in the header, or a blurred placeholder for a hero image. A 20×20 blurred JPEG at around 300 bytes inlined as a background gives you a placeholder that appears with the HTML, which is a real perceived-performance win.
Inlining a font subset removes a request but blocks rendering on the CSS, defeats font-display: swap, and prevents the font being cached across pages. The better move is a subset WOFF2 (pyftsubset or glyphhanger can take a font from 120 KB to under 20 KB by dropping unused glyph ranges), served with preload and font-display: swap.
SVG is text, so base64 is pure loss. If you must use a data URI, URL-encode it instead — it stays readable and is usually smaller:
/* wasteful */
background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0i...");
/* better: url-encoded, ~25% smaller, still valid */
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'...%3E");
Note the single quotes inside the SVG — double quotes must be escaped and bloat the string. Better still, put the <svg> element directly in the HTML: it can inherit currentColor, be styled by CSS, and carry accessible markup, none of which a data URI can do.
Allowing data URIs means adding data: to img-src or font-src. That is a modest but real widening of your CSP, and it is one reason security-conscious teams keep assets external by policy. Never allow data: in script-src.
Build both versions and compare Largest Contentful Paint and total transferred bytes on a throttled connection in Lighthouse, on a repeat visit as well as a cold one. Inlining almost always looks better on the cold-load number and worse on the repeat visit; whether that trade is right depends on whether your audience comes back.