web3icons

Optimizing SVG files without breaking them

Which parts of an exported SVG you can strip safely, which ones you must keep, which edits look harmless but are not, and how to verify the result.

5 min read

An SVG straight out of a design tool often carries far more markup than it needs. Most of the extra is harmless leftovers from the editor. Some of it looks like leftovers but is holding the drawing together. Optimizing well means knowing which is which, and then checking with your eyes, not just the byte count.

An export, before and after

Here is the kind of file a design tool produces for a simple coin icon:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generator: design tool export -->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
     version="1.1" id="Layer_1" x="0px" y="0px" width="32px" height="32px"
     viewBox="0 0 32 32" xml:space="preserve">
  <metadata>Exported from Untitled.design</metadata>
  <defs>
    <linearGradient id="unused-gradient">
      <stop offset="0" stop-color="#ffffff"/>
    </linearGradient>
    <clipPath id="clip0">
      <rect width="32" height="32" fill="white"/>
    </clipPath>
  </defs>
  <g id="Old_version" display="none">
    <circle cx="16" cy="16" r="15"/>
  </g>
  <g clip-path="url(#clip0)">
    <g>
      <circle cx="16.000000" cy="16.000000" r="16.000000" fill="#1F2937"/>
      <path d="M16.000001 5.999998L22.999999 16.000002L16 26L9.000001 16Z" fill="#FFFFFF"/>
    </g>
  </g>
</svg>

And the same drawing, optimized:

<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
  <circle cx="16" cy="16" r="16" fill="#1f2937"/>
  <path d="M16 6l7 10-7 10-7-10z" fill="#fff"/>
</svg>

Both render identically. The second is a fraction of the size and much easier to read. Here is what went, and why each removal was safe.

Safe to remove

What Why it can go
XML declaration (<?xml ...?>) Optional for UTF-8 files, and ignored when SVG is inlined in HTML
Comments Never rendered
<metadata> and editor namespaces Only the editor reads them
version, xml:space, x="0px" y="0px" on the root No effect on a standalone icon
Hidden layers (display="none") Not drawn, as long as nothing references them
Empty or pointless groups A <g> with no attributes changes nothing
Unused <defs> entries A gradient nobody points to is dead weight
Clip paths equal to the full canvas The viewBox already clips to the same rectangle
Excess decimals 16.000001 and 16 are the same at any realistic size
Long hex colors and default values #FFFFFF is #fff; fill-opacity="1" is the default

Two of those come with conditions. A hidden element is only removable if no url(#...) or href="#..." points into it. A clip path is only removable if it really covers the whole viewBox; one that is even slightly smaller is doing real work.

On precision: in a 24 or 32 unit viewBox, two decimal places is plenty. A difference of 0.01 units is about a tenth of a pixel even when the icon is displayed at 256 px. Files drawn on a large canvas, such as a 2000-unit viewBox, can drop decimals entirely.

Must keep

The viewBox. Without it the icon stops scaling. Some optimizers have, depending on the version and settings, removed the viewBox when width and height are present. Always check that it survives.

The xmlns attribute in standalone files. A .svg file loaded via <img>, CSS or a direct URL needs xmlns="http://www.w3.org/2000/svg". Inline SVG in HTML does not, but leave it in your source files so they work in both places. If the file uses xlink:href, the xmlns:xlink declaration must stay too.

Every ID that is referenced. Gradients, clip paths, masks, filters and <use> elements are wired together by ID: fill="url(#g1)", clip-path="url(#c)", href="#shape". So are accessibility hooks such as aria-labelledby. Remove the target and the reference silently fails, usually leaving a shape unfilled or unclipped.

Fill rules. fill-rule="evenodd" and clip-rule="evenodd" decide where holes appear. Drop them and a coin with a cut-out can turn into a solid disc.

Paint order. Later elements draw on top of earlier ones. Reordering shapes changes the picture whenever they overlap.

A <title> you added on purpose. Some optimizer presets remove titles. If your inline icons rely on them for a name, keep them.

Edits that look safe but are not

Some optimizations are correct in general but can change specific files. These deserve a visual check every time:

  • Merging paths. Combining several paths into one can change how overlaps behave, especially with evenodd, and it destroys separate colors if the paths had different fills.
  • Collapsing a group that has opacity. opacity="0.5" on a group fades the group as a single image. Pushed down to each child, the overlapping parts become darker than before.
  • Rounding too hard. One decimal in a 24-unit icon can shift points by up to 0.05 units. That is usually fine, but small curves and thin gaps can visibly warp.
  • Shortening IDs. Renaming coin-gradient to a is harmless in a standalone file. Inline two different icons that both use a, and the second one paints with the first one's gradient. See SVG ID collisions before enabling this for anything you inline.
  • Removing width and height. The drawing is unchanged, but the file no longer has a default size, so an <img> without size attributes or CSS may render at an unexpected size.
  • Converting a <style> block to attributes. Fine for a standalone file, but if the page's CSS was meant to override those values, attributes and inline styles have different priority.

Check the output visually

Byte savings tell you nothing about correctness. Look at the result at several sizes (16, 24, 64 and 256 px) on both light and dark backgrounds.

For larger batches, a difference overlay finds changes your eyes would miss. Put the original and optimized files on top of each other with a difference blend: identical pixels cancel out to black, and any change lights up.

<div class="diff">
  <img src="before/coin.svg" alt="">
  <img src="after/coin.svg" alt="">
</div>
.diff { position: relative; width: 256px; height: 256px; background: #000; }
.diff img { position: absolute; inset: 0; width: 100%; height: 100%; }
.diff img + img { mix-blend-mode: difference; }

A faint outline along edges is normal anti-aliasing noise from rounded coordinates. A solid shape or a color patch means something changed.

When you process hundreds of files, automate the same idea: render each file to PNG before and after with a headless browser, compare pixels, and flag any file whose difference exceeds a small threshold.

Doing it repeatably

Hand-cleaning is fine for one logo. For a library, use a tool and keep the configuration in your repository so every file is treated the same way. SVGO is the standard choice; SVGO settings that are safe for icons walks through a configuration that applies the "safe" list above and avoids the risky ones. For deciding how small is small enough, see how big an icon SVG should be.

Keep the unoptimized source files too. Optimization is lossy in the sense that layer names, editable shapes and comments are gone, and you will want them the next time the logo needs work.

More in SVG

All guides