Base64 Data URIs — When They Help and When They Hurt LCP
A teammate inlined a 400KB logo as a data URI “to save a request.” Lighthouse screamed. LCP moved later. The HTML cache grew forever. Base64 is useful — it is not free bandwidth. Understanding encode/decode and data URI shape helps you use it where it pays rent and refuse it where it taxes every page view.
What Base64 actually is
Base64 maps binary to ASCII using a 64-character alphabet. Decode reverses it. No key, no confidentiality, no integrity check. Anyone who can see the string can recover the bytes.
data:[<mime>][;charset=<charset>][;base64],<payload>
<img alt="" width="16" height="16"
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAA..." />
Paste bytes into a Base64 encode/decode tool and compare lengths before you commit. If the encoded string is longer than your HTML budget for the whole page, stop.
Size reality check
| Asset | Binary | Approx. Base64 text | Verdict |
|---|---|---|---|
| Tiny SVG / 1×1 GIF | <1KB | ~1–2KB | Fine inline |
| 16–32px icon PNG | 1–4KB | 2–6KB | Often OK in critical CSS |
| Logo 80KB WebP | 80KB | ~107KB | Separate file + cache |
| Hero 300KB JPEG | 300KB | ~400KB | Never inline |
Extra costs people miss:
- HTML cannot cache the image separately from the document
- Every page that includes the blob redownloads it
- git diffs become walls of noise; code review suffers
- CSP may restrict
data:inimg-srcorfont-src - Compression of HTML helps, but you still parse a huge string on the main thread
When inlining helps vs hurts LCP
Helps: critical above-the-fold icons, tiny email assets, self-contained Storybook fixtures, small SVG masks, critical CSS background sprites under a few KB.
Hurts: anything on the HTML critical path that could have been a preloadable URL. Prefer:
<link rel="preload" as="image" href="/logo.webp" type="image/webp" />
<img src="/logo.webp" width="120" height="40" alt="Brand" />
Optimize first (SVGO, tiny PNG, WebP/AVIF), then decide. Inlining a fat PNG is two mistakes stacked. For LCP candidates, a real URL also lets the browser prioritize, cache across navigations, and respond to srcset.
Debugging garbled payloads
- Strip whitespace and email-style line wraps (
\r\nevery 76 chars). - Fix URL-safe
-/_versus standard+//. - Check padding (
=/==) — some encoders omit it. - Confirm it is Base64, not hex or quoted-printable.
- Verify the MIME in the data URI matches the magic bytes after decode.
JWT uses Base64URL without padding; image data URIs usually use standard Base64 with padding. Mixing them yields “decodes but PNG header is wrong.”
function normalizeB64(s) {
let t = s.replace(/\s+/g, "").replace(/-/g, "+").replace(/_/g, "/");
while (t.length % 4) t += "=";
return t;
}
MIME and charset gotchas
| Content | MIME notes |
|---|---|
| PNG / JPEG / WebP | image/png, image/jpeg, image/webp |
| SVG | image/svg+xml — URL-encode if not using ;base64 |
| Fonts | font/woff2 — almost always better as files |
| JSON | application/json;base64,... — rare; prefer fetches |
Wrong MIME → broken preview even when bytes are correct. SVG-as-data-URI without encoding # and < breaks in CSS url(). If Lighthouse blames a huge document, search HTML/CSS for data:image — one CMS-inlined screenshot can outweigh a week of JS trimming.
CSS backgrounds vs <img>
Inlined backgrounds in CSS bundles get downloaded with every stylesheet revision. Critical CSS with a 2KB icon can be fine; dumping product photos into app.css couples image updates to CSS cache busts. Prefer <img> or CSS referencing hashed files in /assets.
Security misconceptions
- Base64 is not encryption — never ship API keys or session tokens as “hidden” data URIs.
- Decoding untrusted Base64 into
evalorinnerHTMLis still XSS if the payload is active content. - Email clients and PDFs embed images as Base64 for portability; web pages have HTTP caches — use them.
Practical decision checklist
- Encoded size measured (not guessed)
- Under ~4–8KB unless you have a measured exception
- Not an LCP hero or photo
- CSP allows
data:only where required - Prefer SVG source over Base64 PNG when vector works
- Secret material never encoded “for safety”
Rule of thumb
Measure encoded length. Over ~4–8KB → real URL unless profiling proves otherwise. Never Base64 “encrypt” credentials. Inline the postage stamp; host the billboard.