SVG Optimization: Why Your Vector Files Are 10× Bigger Than They Need to Be
Editor metadata, path precision, and needless groups bloat exported SVGs. What is safe to strip, what breaks, and when an SVG should be inline, external, or a data URI.
Where the bloat comes from
Design tools export SVGs for re-editing, not for shipping. A typical Illustrator or Figma export carries editor namespaces, layer names, hidden elements, empty groups, and — the largest offender — path coordinates at 6+ decimal places. On a 24px icon, coordinates beyond 2 decimals are sub-perceptual: rounding them routinely cuts file size 40–60% with zero visible change.
- Safe to strip: editor metadata, comments,
<title>/<desc>you did not write, default-value attributes, empty groups. - Strip with care:
viewBox(never remove it — it is what makes SVG scale), IDs referenced by<use>, gradients or masks, and accessibility titles you added deliberately. - Transform flattening: merging nested
transform="translate(…)"chains into path data shrinks files but breaks CSS that targeted those groups.
Inline, external file, or data URI?
Inline SVG (pasted into HTML) can be styled and animated with CSS and inherits currentColor — right for icons and logos that respond to theme. External .svg files cache across pages — right for large illustrations. Data URIs embed the SVG inside CSS, avoiding a request but bypassing caching and bloating the stylesheet — right only for tiny, single-use decorations like background patterns.
One rule cuts across all three: gzip loves SVG. The XML is repetitive text, so a 30 KB optimized SVG often ships as 6 KB over the wire. Optimize first, but do not chase bytes the compressor already removes.
Making SVG icons accessible
A decorative icon next to a visible label should be hidden from assistive technology: aria-hidden="true" plus focusable="false" (the latter for older Edge/IE, which made SVGs tab stops). A *meaningful* icon — the only content of a button, say — needs an accessible name: either a <title> as the first child of the SVG with role="img" and aria-labelledby pointing to it, or, more robustly, an aria-label on the button itself and aria-hidden on the SVG. The common failure is neither: an icon-only button with no name, read aloud as "button".
Color: use fill="currentColor" so the icon inherits text color and automatically follows hover, disabled and dark-mode states without extra CSS — and so forced-colors (Windows High Contrast) mode can recolor it.
Sprites vs inline vs icon fonts
Icon fonts had a decade and are effectively over: they are hacks on text rendering, they fail when fonts are blocked, they anti-alias badly, and they cannot be multi-colored. Inline SVG is the right default for a handful of icons per page. For dozens, an SVG sprite — one file of <symbol> elements referenced with <use href="sprite.svg#icon-search"> — keeps the DOM small and caches across pages, at the cost of slightly more awkward styling (CSS custom properties pass through <use>; direct child selectors do not). Frameworks that compile icons to components get the best of both: tree-shaken, inline, typed.
- Keep every icon on the same grid (16, 20 or 24 px) with the same stroke width so a set feels like a set.
- Optimize the sprite as a whole; duplicate gradient and mask IDs across symbols are the most common sprite bug.
- Set
widthandheighton the<svg>or<use>consumer — an unsized SVG defaults to 300×150.
SVGs in React and the JSX conversion problem
JSX rejects hyphenated attributes, so stroke-width must become strokeWidth, class becomes className, and inline style strings must become objects. Doing this by hand for a 40-path illustration is misery; automated conversion is the only sane path.