Skip to content
yutils

SVG Optimization — From 50KB Editor Export to 5KB Production-Ready

Why editor exports balloon, what SVGO removes, when to inline vs reference, sprite sheets, and the security checklist for user-uploaded SVGs.

~7 min read

Export an SVG from Figma or Illustrator and you usually get 40–80 KB. Run it through SVGO and the same graphic becomes 5–10 KB. Almost all of the difference is editor metadata. This guide covers what editors stuff into SVGs, what's safe to strip, when to inline vs reference, sprite sheets, and how to accept user-uploaded SVGs without opening up XSS.

What editors leave behind

A typical Figma export:

<svg width="24" height="24" viewBox="0 0 24 24" fill="none"
     xmlns="http://www.w3.org/2000/svg">
  <!-- Generator: figma -->
  <metadata>
    <rdf:RDF xmlns:rdf="...">...</rdf:RDF>
  </metadata>
  <defs>
    <clipPath id="clip0_1_2">
      <rect width="24" height="24" fill="white" transform="translate(0)" />
    </clipPath>
  </defs>
  <g clip-path="url(#clip0_1_2)">
    <path d="M12 2C6.48..." fill="#0F172A" />
  </g>
</svg>

Safe to remove:

  • <metadata>, <sodipodi:*>, <inkscape:*> — editor-only namespaces.
  • XML comments.
  • Default attributes — fill="none", stroke-linecap="butt", etc.
  • Empty <defs>, empty <g>.
  • Unused clipPath, mask, filter.
  • Excess precision in path data — d="M 12.0000001 2.0000003" M 12 2.
  • Auto-generated IDs like clip0_1_2.
  • The XML declaration (<?xml ... ?>) — useless when inlined in HTML.

SVGO in one command

npm i -D svgo
npx svgo icon.svg -o icon.optimized.svg

# Whole folder
npx svgo -f icons/ -o icons-min/

# Config (svgo.config.js)
module.exports = {
  multipass: true,
  plugins: [
    "preset-default",
    {name: "removeViewBox", active: false},  // keep viewBox (required for responsiveness)
    {name: "removeDimensions", active: true}, // drop width/height so CSS controls size
  ],
};

For visual tuning, SVGOMG (https://jakearchibald.github.io/svgomg/) shows each option's effect interactively.

Keep viewBox, drop width/height

Editor exports often have width="24" height="24" viewBox="0 0 24 24". Same information three times. Keep only viewBox and the SVG becomes a size-agnostic vector that CSS can scale freely.

<!-- Recommended -->
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
  <path d="..." />
</svg>

<!-- CSS -->
.icon { width: 24px; height: 24px; }
.icon-large { width: 48px; height: 48px; }

Inline vs external

Inline SVG — preferred for small icons

<button>
  <svg viewBox="0 0 24 24" aria-hidden="true">
    <path d="..." />
  </svg>
  Copy
</button>
  • Pros: zero HTTP requests. CSS currentColor tracks theme. JS can mutate a single path.
  • Cons: repeating the same icon means repeating HTML bytes. Gzip compresses it well, but at dozens of repetitions it adds up.

External SVG — large illustrations

<img src="/hero.svg" alt="..." width="800" height="400" />
<!-- or -->
<object data="/diagram.svg" type="image/svg+xml"></object>
  • Pros: browser cache. One download, reused everywhere.
  • Cons: SVG inside img can't be styled or scripted from outside.

Data URI inline

Embed SVG as a base64 or URL-encoded data URI, typically in CSS backgrounds.

background-image: url("data:image/svg+xml,%3Csvg ... %3E");

Image to Base64 (Data URI) produces data URIs from SVG too. URL-encoded is ~30% smaller than base64 for SVG — prefer it.

Sprite sheets — many icons

With 20+ icons and inlining starting to hurt, put them into a single SVG of <symbol> elements and reference with <use>.

<!-- sprite.svg -->
<svg xmlns="http://www.w3.org/2000/svg">
  <symbol id="i-search" viewBox="0 0 24 24">
    <path d="..." />
  </symbol>
  <symbol id="i-trash" viewBox="0 0 24 24">
    <path d="..." />
  </symbol>
</svg>

<!-- Usage -->
<svg class="icon"><use href="/sprite.svg#i-search" /></svg>
<svg class="icon"><use href="/sprite.svg#i-trash" /></svg>

The browser fetches sprite.svg once. fill: currentColor for theming. Cross-origin sprites need CORS.

Colors and theming — currentColor

Replace hard-coded colors with currentColor and the SVG inherits the CSS color property.

<!-- Before -->
<svg viewBox="0 0 24 24"><path d="..." fill="#0F172A" /></svg>

<!-- After -->
<svg viewBox="0 0 24 24"><path d="..." fill="currentColor" /></svg>

/* CSS */
.icon { color: var(--fg); }
.icon:hover { color: var(--accent); }
[data-mode="dark"] .icon { color: var(--fg-dark); }

Naturally pairs with dark mode and theme systems — lucide-react, heroicons, and most icon libraries use this.

User-uploaded SVG — XSS defense

SVG can contain HTML and JS. Rendering uploads directly is an XSS surface.

<!-- Malicious SVG -->
<svg xmlns="http://www.w3.org/2000/svg">
  <script>alert(1)</script>
  <a href="javascript:alert(1)"><text>click</text></a>
  <image href="x" onerror="alert(1)" />
</svg>

Defenses:

  1. Don't inline — render with <img src="upload.svg">. Scripts inside an <img> don't run.
  2. If inlining is unavoidable, sanitize with DOMPurify (server or client).
  3. Serve uploaded SVGs from a separate domain (e.g. uploads.example.com) so cookies are isolated.
  4. CSP script-src 'self' + object-src 'none' as a backstop.

Icon library vs roll-your-own

2026 standard picks:

  • lucide-react — open source, tree-shaken, 1500+ icons. Most common choice. About 1 KB per icon.
  • heroicons — by the Tailwind team. 200+ icons with outline/solid variants.
  • radix-icons — consistent weight, design-system friendly.
  • tabler-icons — 4500+ free icons.

Rolling your own: standardize on viewBox 24×24 or 16×16, consistent stroke width (1.5 or 2), uniform line joins. Visual consistency in a design system multiplies once you have many icons.

Common pitfalls

1. removeViewBox by accident

SVGO's preset-default removes viewBox by default, breaking responsiveness. Always set removeViewBox: false.

2. Losing precision

SVGO defaults to precision 3. Fine for icons; can shift detailed illustrations visibly. Use floatPrecision: 5 when needed.

3. Mixing fill and stroke styles

Outline icons use stroke (no fill), solid icons use fill (no stroke). Mixing within one component breaks color control.

4. JSX attribute kebab → camelCase

In React, stroke-widthstrokeWidth, clip-pathclipPath. Using SVG attribute names directly produces warnings.

5. Missing aria-hidden

When an icon's meaning is also in text (a "Copy" button with a copy icon), add aria-hidden="true" so screen readers don't repeat it.

6. CSP conflicts

Inline style attributes or <style> inside SVG can violate a strict style-src 'self' policy. Externalize the CSS or allow 'unsafe-inline' for styles.

Summary

  • Editor export → SVGO trims 60–80% of the file.
  • Keep viewBox, drop fixed width/height — let CSS size it.
  • Inline small icons; externalize and cache large illustrations; spritesheet 20+ icons.
  • Use fill="currentColor" for automatic theming.
  • For user uploads, render with <img> or DOMPurify-sanitize inline content.
  • A library (lucide-react, etc.) usually beats hand-rolling.
Back to guides