Base64 Data URIs — When They Help and When They Hurt LCP

webdev base64 performance data-uri tools

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

AssetBinaryApprox. Base64 textVerdict
Tiny SVG / 1×1 GIF<1KB~1–2KBFine inline
16–32px icon PNG1–4KB2–6KBOften OK in critical CSS
Logo 80KB WebP80KB~107KBSeparate file + cache
Hero 300KB JPEG300KB~400KBNever 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: in img-src or font-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

  1. Strip whitespace and email-style line wraps (\r\n every 76 chars).
  2. Fix URL-safe - / _ versus standard + / /.
  3. Check padding (= / ==) — some encoders omit it.
  4. Confirm it is Base64, not hex or quoted-printable.
  5. 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

ContentMIME notes
PNG / JPEG / WebPimage/png, image/jpeg, image/webp
SVGimage/svg+xml — URL-encode if not using ;base64
Fontsfont/woff2 — almost always better as files
JSONapplication/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 eval or innerHTML is 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.