Optimising SVGs safely: what to remove and what breaks

What an SVG export carries that browsers never use, how much removing it saves raw and gzipped, and the five "optimisations" that quietly break icons.

On this page

An SVG straight out of a drawing program is often two or three times larger than it needs to be. Most of the extra is safe to remove. A few common optimisations, though, change the picture under conditions you will not see in a quick test: a sprite icon loses its colour, a hover style stops working, a long path drifts. This article goes through both with a real example, using the SVG optimizer, which applies only the safe versions and checks the result pixel by pixel.

What an export carries

The example built into the optimizer is a 48 × 48 icon (a rounded square with a gradient, a chart line and a dot) written the way Inkscape saves files. It contains:

  • an XML declaration and a "Created with" comment;
  • Inkscape's sodipodi:namedview element (window size, zoom level, page colour) and inkscape:label / inkscape:groupmode attributes;
  • a <metadata> block;
  • an unused gradient left over from an earlier version;
  • numbers such as 4.0000000 and 16.0000001;
  • id attributes on every element;
  • version="1.1" and opacity="1", both defaults;
  • a layer group with nothing but editor attributes.

None of it affects how the icon looks in a browser.

The result

The optimiser removed one comment, one metadata block, one editor element and four editor attributes, shortened twelve numeric attributes, dropped two defaults, four unused IDs, one unused gradient and the empty layer group. The file went from 1,348 bytes to 503, 62.7% smaller. Gzipped, which is what a web server sends when compression is on, it went from 630 to 311 bytes, 50.6% smaller. The gzip saving is always smaller than the raw one, because gzip already compresses the repetitive parts; it is the number that matters for page speed.

The path M 14.000000,30.000000 L 21.000000,22.000000 L 26.000000,27.000000 L 34.000000,18.000000 became M14 30L21 22 26 27 34 18: same points, shortest form (repeated command letters can be left out).

The pixel check drew both versions in the browser and compared them: identical.

Five optimisations that break things

1. Removing "default" fills inside symbols. fill="black" is the initial value, so removing it looks safe. But in a sprite, a <use> element can set fill="red", and the content of the symbol inherits it. A path that explicitly says fill="black" stays black; remove the attribute and it turns red. The optimizer keeps default attributes inside <defs>, <symbol> and anything a <use> points to.

2. Removing defaults when a parent or a style sets the property. In <g fill="red"><path fill="black"/></g> the "default" on the path overrides the group. With <style>g{fill:red}</style> the same thing happens through CSS. The optimizer only removes an inherited default when no ancestor sets it and the file has no <style> block.

3. Removing IDs that CSS, animation or accessibility use. An ID can be referenced by href="#icon", by url(#gradient) in an attribute or in CSS, by a selector such as #dot:hover in a <style> block, by an animation's begin="fade.end", or by aria-labelledby. All of these are checked. Two cases cannot be proven from the file: sprite symbols used from an HTML page, and page CSS or JavaScript that targets IDs inside an inline SVG. The optimizer never removes <symbol> IDs, keeps every ID when the file has a <script>, and lets you switch ID removal off.

4. Collapsing whitespace inside text. Between tags, whitespace is meaningless. Inside <text>Hello world</text> it is part of the content, and under xml:space="preserve" every space counts. The optimizer never touches text content.

5. Rounding relative path data naively. Many paths are written with relative commands (l, c): each step continues from where the last one ended. Round each step to three decimals and the rounding errors add up. In the extreme case of 1,000 steps of 0.0004 units, each rounds to 0 and the path ends 0.4 units short. The optimizer writes each step as the difference between the exact target and where the rounded path has actually got to, so the error never exceeds half of the last decimal, however long the path. Its tests check exactly that case.

Choosing the precision

Three decimals is safe for icons on a 24 or 48 unit grid: drawn at 512 pixels, a thousandth of a unit is 0.02 pixels on a 24-unit icon and 0.01 on a 48-unit one. Illustrations with a viewBox of several thousand units can use one or two. Drawings with a tiny viewBox (0 to 1) need four or more. If the pixel check reports a difference, raise the precision.

Common mistakes

  • Removing `<title>`. It is how screen readers name an SVG. Keep it unless the SVG is purely decorative and marked aria-hidden.
  • Removing the viewBox. Without it the SVG cannot scale. The optimizer never removes it, and adds one when only width and height are set.
  • Trusting the size without looking. Always compare before and after; the optimizer's pixel check does it for you.
  • Optimising the master file. Keep the editor file and export an optimised copy; the editor data is what lets you keep working on it.

Recipe

  1. Export from your editor as plain SVG.
  2. Open it in the SVG optimizer with the defaults: 3 decimals, editor data and comments removed, ID removal on unless a page script uses the IDs.
  3. Check the pixel result and the gzip size.
  4. If you need a PNG or an icon set as well, the SVG converter draws the optimised file at any size.

Tools mentioned in this article

Back to the blog