Open an SVG straight out of Illustrator and you’ll find a lot that has nothing to do with the drawing. Editor metadata, comments, hidden layers, coordinates carried out to six or seven decimals. A browser needs none of it.

Stripping that out is what people mean by SVG optimization. The picture renders the same and the file gets smaller. Most front-end teams do it with SVGO, a Node.js library and command-line tool, usually on icons, logos and illustrations exported from Sketch or Adobe Illustrator.

One change trips up older guides. As of October 2026, SVGO v4 no longer removes the viewBox attribute or title elements by default, according to its v3 to v4 migration guide.

So the common advice to switch removeViewBox off describes a default that no longer exists.

What does SVG optimization remove from a file?

Mostly text the browser never needed. SVG files are XML documents, so all of this happens on markup, not pixels. Image compressors built for PNG or JPEG have nothing to grab here.

Vector editors write that markup for themselves, not for browsers. SVGO’s introduction docs say exports usually carry editor metadata, comments, hidden elements and default or suboptimal values that can go without changing the render.

The doctype, the XML processing instruction, comments, the metadata element and editor namespace data are safe to remove in nearly every case, and all five run in SVGO’s default preset. One catch. License text stored in metadata goes with it, so copy that out first.

Hidden elements and empty containers come out too, though you’d want to keep them if a script or animation switches them on later.

Unused and shortened IDs are fine unless the page touches them. SVGO’s cleanupIds backs off when a script or style element is present, since either could reference an ID.

Descriptions and titles need more care. removeDesc deletes only empty descriptions or editor attribution, and removeTitle is no longer a default plugin in SVGO v4.

Shapes and curves don’t show up anywhere above. They only change when numbers get rounded, and that’s a separate decision.

How does path data precision affect SVG size?

SVGO’s convertPathData plugin rounds path coordinates to 3 decimal places by default, set through its floatPrecision parameter. Every coordinate is a number in the d attribute, and the more decimals it keeps, the more characters you ship.

Path data lives in the d attribute, and each character in there is a drawing instruction. According to SVGO’s documentation, convertPathData goes after it from several angles.

  • Relative or absolute coordinates, whichever comes out shorter
  • Curves that behave like straight lines become line commands
  • Commands that move to the current position are dropped
  • Redundant delimiters and leading zeros get trimmed
  • Numeric values are rounded

Of those, rounding is the one worth tuning, since it’s the only step that can visibly change the drawing.

You can trim digits by hand when SVG files get edited one at a time, and for a single icon that’s fine. Past that, a plugin applying the same rule across a whole set is far less work.

Lossless and lossy settings

Comments, metadata, the doctype, whitespace, delimiters and unused IDs all sit on the lossless side. Decimal precision in coordinates is where loss starts, along with the swap of near-straight curves for straight lines.

Tool docs are fairly honest about this. svgcleaner documents itself as strictly lossless by default, and Scour describes its options as typically lossless but tunable for more aggressive cleaning. SVGO’s default run already rounds to three decimals, so “lossless” there means no visible change, not identical coordinates.

When low precision distorts a path

Rounding error scales with render size, not with the number stored in the file.

Say you have an icon drawn in a 24 by 24 viewBox. Rounding to 1 decimal place moves a point by at most 0.05 units, about 0.2% of the icon’s width.

At 24px that’s 0.05px, which no one sees. At 480px the same file moves points by up to 1px, and curves start to look faceted.

So set precision for the largest size the graphic will ever render, not the size it renders today. Drop one decimal at a time, compare at that large size, and stop when a curve shows its corners.

How much smaller does an SVG get after optimization?

It depends heavily on the export. In a 2021 LogRocket test of SVGO’s default settings, cumulative savings ran from 23.7% to 56% across three real asset sets, and the article puts the full range from a low single-digit percentage up to 90 percent.

Sketch and Illustrator files respond best, since they carry long names, comments and over-precise numbers. Larger illustration files shrink less.

The Sketch-made FlagKit icon set dropped from 793kB to 346kB, a 56% cumulative saving (LogRocket, 2021). An Illustrator pack called Illlustrations went from 3.4MB to 2.6MB, which is 23.7%. A single 1.3MB world map file landed at 880kB, saving 31.5%.

Separately, the KDE Breeze icon theme went from 21.72 MiB to 11.09 MiB, a 48.91% cut, measured with svgcleaner (its README).

The two sources don’t contradict each other, really. Different tools, different files, and the savings follow how much editor overhead each one started with.

Transfer compression narrows the gap further. With Brotli level 3 applied (the level LogRocket observed in its test; Cloudflare’s own documentation lists level 4 as its default), the absolute savings from minifying fell to 72kB for FlagKit and 122kB for the illustration pack, with the map saving 132kB.

That makes a before-and-after byte table overstate what a visitor actually downloads.

LogRocket’s own verdict runs against treating this as a first-priority fix. It concludes that minifying SVGs rarely moves page performance unless the files are large and visible above the fold. I’d agree with that for most sites.

Which SVG optimizer should you use?

SVGOMG for a one-off file, SVGO in the build for anything you will run twice. Scour and svgcleaner only fit narrow cases.

ToolRuns asConfigurationBest fit
SVGONode.js CLI and librarysvgo.config.mjs, plugin by pluginBuilds, batches, repeat runs
SVGOMGBrowser GUI for SVGOSVGO options as togglesOne-off files, quick checks
ScourPython CLICommand-line flagsPython-based pipelines
svgcleanerRust CLI, archivedLossless by defaultLegacy scripts only

SVGOMG is SVGO’s Missing GUI, built by Jake Archibald, and its repository README says it aims to expose most of SVGO’s configuration options. Every option is a toggle, which suits a quick check on a single file.

Not every online optimizer runs SVGO, so check the engine behind a web tool. Aspose’s tool is built on its own Aspose.SVG .NET library, which means its output won’t match an SVGO run. Aspose says it deletes uploaded files after 24 hours.

I’d skip svgcleaner for new work. Its owner archived the repository on October 30, 2021, and it is read-only. Lossless by default is still a sound design, but an unmaintained tool makes a poor base for a build step.

Scour is a Python optimizer under the Apache-2.0 license, and its stated goal is output that renders identically at a fraction of the size. It earns a place when your toolchain is already Python and adding Node would be the heavier choice.

How to optimize an SVG with SVGO

Start with a global install.

  1. Install it globally with npm install -g svgo. SVGO v4 requires Node.js 16 or newer.
  2. Run it on one file with an output path, for example svgo one.svg -o one.min.svg, so the original stays untouched.
  3. For a whole folder, run svgo -rf path/to/svgs -o path/to/output. The -r flag recurses and -f takes the folder.
  4. Add a svgo.config.mjs file to the project root. SVGO reads it automatically, or you can point to another file with –config.
  5. Set multipass to true in that file to run the passes again over their own output.
  6. Compare the result with the original at the largest size the graphic will render, then commit the smaller file.

Configuring plugins

SVGO’s default preset runs more than 30 plugins in a fixed order, starting with removeDoctype and ending with removeDesc (SVGO documentation). You can keep the preset and override single plugins, or drop it and list only the plugins you want.

export default {
multipass: true,

plugins: [
{
name: 'preset-default',
params: {
overrides: {
convertPathData: { floatPrecision: 2 },
},
},
},
],
};

That keeps the default preset, turns on multipass and lowers path precision to 2 decimals for the project. The precision line is the one worth testing. I’d leave the rest of the preset as shipped.

The SVGO v4 change to viewBox and title

SVGO’s v3 to v4 migration guide, as of October 2026, takes two plugins out of the default preset. removeViewBox went to preserve scalability, and removeTitle went to preserve accessibility.

Guides written for v3 still tell readers to switch removeViewBox off. On v4 it’s already off unless you add it back.

The plugin’s own page carries a danger notice. Without a viewBox the graphic stops scaling to its container and can clip when the container is small. Leave it off. The few bytes a viewBox costs are not worth a graphic that no longer fits its box.

Titles and descriptions matter to anyone on a screen reader, which is why accessible SVGs depend on keeping them. SVGO’s removeDesc deletes only empty descriptions or editor attribution unless you set its removeAny parameter.

When does SVG optimization break an image?

Usually when a plugin changes something the page still depends on. A shared ID, a hidden element that a script reveals later, a class that page CSS targets, a style block covered by a security policy.

Common failure modes and fixes

SymptomPlugin behind itFix
Shapes pick up another graphic’s gradient or maskcleanupIds (minify)Add prefixIds, or set minify to false
Page CSS stops matching classes inside the SVGprefixIds (prefixClassNames)Set prefixClassNames to false
Animated or script-driven parts vanishremoveHiddenElems, removeScriptsOverride or drop the plugin for that file
Style element rewritten under a CSP hashinlineStylesSet inlineStyles to false
Graphic clips or stops scalingremoveViewBox, if added backDelete it from the plugin list

cleanupIds shortens IDs with a predictable algorithm, so separate files run through SVGO can end up with identical IDs once they are inlined in one document (SVGO documentation). The docs cite a Docusaurus bug report as the reference case.

The usual fix, the prefixIds plugin, has a side effect. It prefixes class names as well as IDs by default, so page CSS that targets those classes stops matching until prefixClassNames is set to false.

Animation is the other common casualty. Astro’s documentation warns that the default preset can clean up too aggressively, especially with animations. If the graphic relies on animating an SVG with CSS, turn off removeHiddenElems for that file, and keep removeScripts out of any interactive graphic, since it strips script elements, event handlers and script URIs.

None of this is new. Mozilla Hacks noted the same class of problem back in 2015: complex images could come out garbled after optimization, mainly because of two SVGO plugins. mergePaths caused trouble where paths overlapped, and convertPathData’s relative and shorthand notation misrendered in some non-browser viewers, though browsers displayed it correctly.

How to verify an optimized file

A pixel diff catches what a file-size check can’t.

Playwright’s toHaveScreenshot assertion writes a reference image on the first run and compares later runs against it with pixelmatch. Its threshold defaults to 0.2 on a scale from 0 (strict) to 1 (lax), and maxDiffPixels stays unset until you set it (Playwright documentation).

For SVG work, the baseline is the original file at the smallest and largest size it ships at, and the candidate is the same file after SVGO. Set maxDiffPixels to 0 for icons, where a one-pixel shift is a real change.

Playwright keeps separate reference images per browser and platform because rendering differs, which is the practical side of cross-browser compatibility. It also warns that results shift with OS, hardware and headless mode, so generate baselines in the same environment that runs the tests.

How do gzip, Brotli and SVGZ combine with SVG optimization?

They stack. Optimization shrinks the source markup once at build time, and gzip or Brotli shrinks every response on the wire.

Brotli compresses SVG better than gzip, while gzip compresses faster. SVGZ is a pre-gzipped file that still needs the right response header.

gzip is supported by all major browsers, and it’s the format Lighthouse uses to estimate compression savings. Brotli gets better ratios but compresses more slowly, which is why MDN suggests gzip for non-cacheable content. Static SVG files are cacheable, so they favor Brotli.

SVGZ is just a gzip-compressed SVG file. Browsers read it only when the server sends Content-Encoding: gzip, and on Apache that means AddEncoding gzip svgz (IDRsolutions).

SVG compresses well because it is repetitive XML. IDRsolutions, which builds a PDF-to-SVG converter, reports that some of its generated files shrink by 80 to 90 percent under gzip.

The catch is the MIME type. Compression rules often list text and JavaScript types and forget image/svg+xml, so SVG goes out uncompressed. IDRsolutions points to this omission in many server tutorials.

Brotli isn’t always on by default either. deco found its Cloudflare edge was still sending gzip until Brotli was switched on in settings, and a full day on each in February 2026 showed 27% less bandwidth on its sprites.svg route.

Lighthouse flags text responses that carry no br, gzip or deflate encoding header. It skips responses under 1.4KiB or with under 10% potential savings, and as of Lighthouse 13 the check sits inside the Document request latency insight.

SVGZ buys little. The server skips compression per request, but the header setup is one more thing to get wrong, and a 2019 SVG Working Group issue describes nginx recompressing .svgz files because they share the SVG MIME type.

Serve plain .svg with Brotli and a gzip fallback. Reach for SVGZ only on static hosts where you control headers and cannot compress on the fly.

Inline SVG, img tag, CSS background or sprite: which delivery method fits?

Use inline SVG when the page has to style or script the graphic, an img tag for a standalone picture, a sprite when icons repeat across pages, and a CSS background only for decoration.

MethodCachingScripts and page CSSBest fit
Inline SVGTravels with the HTML documentFull access to page CSS and scriptsThemed icons, animated graphics
img tagOwn file, cached separatelyScripts off, external files not loadedLogos, illustrations
CSS backgroundOwn file, cached separatelySame limits as img, no alt textDecorative patterns
Sprite with symbol and useOne shared file, cached onceStyling flows in through CSS inheritanceLarge icon sets

The limits in the middle two rows come from the browser’s image context, per MDN’s guide to SVG as an image. They do not apply when the file loads through iframe, object or embed.

Inlining an SVG in HTML means pasting the whole file into the document, which keeps every node in the DOM. MDN recommends role=”img” with a title element as the first child, or aria-labelledby, while an img tag takes alt text instead.

With a sprite you keep one file of symbol elements, and each use element points to one by fragment, for example href=”sprite.svg#icon-search”. MDN documents two limits. Browsers may apply the same-origin policy to use, and there is no defined way to set a cross-origin policy for it, so a sprite on a separate CDN domain can fail to load.

IDs need care too. cleanupIds removes IDs that nothing inside the file references, which includes symbols that only the page points to, so protect them with the preserve or preservePrefixes parameters or switch the plugin off for sprite files.

A handful of icons that follow text color usually go inline. A large icon set reused across pages belongs in one sprite, and a logo or a single illustration can just be an img tag.

A hero image is the classic img case, since it shows up once per page and is big enough to earn its own cache entry. Alt text covers accessibility.

How to run SVG optimization in a build pipeline

Run SVGO as a build step, not a manual task. One shared svgo.config.mjs, applied by an npm script or a bundler plugin, means every new designer export passes the same rules.

{
"scripts": {
"svg": "svgo -rf src/icons -o public/icons --config svgo.config.mjs",

"prebuild": "npm run svg"
}
}

The prebuild hook runs before the build script, so the output folder is rebuilt from untouched sources every time.

Keep those sources unmodified. LogRocket’s 2021 analysis names the cost of running SVGO over files you still edit: two sets of files, and harder changes to fills and strokes in the minified copies.

On Vite, vite-plugin-svgr turns SVG files into React components, imported with the ?react suffix. Its README says SVGO needs @svgr/plugin-svgo added to svgrOptions.plugins, with svgoConfig carrying options such as floatPrecision.

SVGR core’s options page lists svgo as true by default and says SVGR implicitly adds prefixIds to reduce ID collisions. The two pages disagree on defaults, and SVGO’s own docs warn that tools wrapping it can run different plugins than preset-default, so inspect a built component. Prefixed IDs mean SVGO ran.

Astro has an experimental svgOptimizer flag, added in astro@5.16.0, that runs SVGO on SVG component imports in production builds and skips it in dev. It covers every SVG component import, with no per-component opt-out.

Astro’s v5 docs name the flag experimental.svgo, while the current docs use experimental.svgOptimizer with the svgoOptimizer() helper, so check which version you run.

Automation hides regressions. Run a Playwright screenshot comparison on every pull request that touches an SVG, and commit the baselines.

A pipeline that only shrinks files and never checks them trades bytes for risk.

SVG Optimization FAQ

Is an optimized SVG always smaller than a PNG?

No. SVG wins on flat graphics such as icons and logos, but a photo traced into thousands of paths can end up larger than the original raster file.

Optimization trims markup, yet it cannot shrink a graphic that is complex by design.

Does SVG optimization improve Core Web Vitals?

Rarely. An SVG loaded through an img tag does not block rendering, while one inlined in CSS does, so that is where file size reaches page load timing (LogRocket, 2021).

Lighthouse also scores paint metrics such as Largest Contentful Paint instead of raw asset size.

How can I tell whether an online optimizer uploads my file?

Open DevTools, switch to the Network tab, then run the optimizer on your file. A request carrying your SVG to another host means the file was uploaded.

Tools built on SVGO’s browser build, imported from svgo/browser, can process the file inside the page instead.

Can I optimize SVGs without installing Node.js?

Yes. SVGOMG, the browser front end for SVGO, needs no installation, and only the command-line version requires Node.js.

Can an HTML minifier shrink inline SVG?

Only partly. An HTML minifier trims whitespace and comments, but it does not round path data or strip editor namespaces, which are SVGO plugin jobs.

Run SVGO on the file first, then let the minifier handle the rest of the page.

Which SVG Optimization Rules Should a Team Standardize First?

A single shared svgo.config.mjs, run by the build, comes first, because SVG optimization only holds when every export passes through identical settings.

After that, the order runs like this.

  • Shared config with a pinned SVGO version
  • Brotli at the edge, with image/svg+xml in the compression list
  • Screenshot comparison on every pull request that touches an SVG

The config decides what bytes every later step has to deal with. Compression then applies to whatever files come out, and the screenshot test watches over both. Major SVGO releases change defaults, which is why the version stays pinned.

The cost is flexibility: a locked config sometimes flattens an animated graphic or a sprite, so per-file overrides go through code review.

As of October 2026, Astro’s svgOptimizer flag is still experimental, and a stable release would fold the first step into framework config.

Bogdan Sandu
Latest posts by Bogdan Sandu (see all)