Shipping SVGs Without Breaking Them: A Sensible SVGO Baseline
SVGO's default preset strips viewBox and can break responsive icons. Here's a minimal, defensible config with multipass, plus how to verify the output before shipping.
14 Dec 2025, 15:35 UTC

Your design team exports icons from a vector editor, and each file arrives stuffed with editor metadata, empty groups, and coordinates like 12.4839201. The obvious fix is SVGO, the Node.js-based SVG optimizer — but the obvious invocation, npx svgo icon.svg with zero configuration, has a well-known failure mode: it strips the viewBox attribute, and suddenly your icons stop scaling responsively. The useful takeaway is that SVGO's defaults need one small override and one deliberate decision about multipass before they're safe to automate.
What the default preset actually does
SVGO works as an ordered pipeline of plugins. Each plugin handles one transformation: removing editor metadata, collapsing redundant groups, converting shapes to shorter path syntax, rounding numeric precision, and so on. The built-in preset-default bundles a curated set of these so you don't have to enumerate dozens of plugins yourself.
The catch is that "curated" means "aggressive about bytes, indifferent to your semantics." Several plugins in the default preset are lossy in ways that matter beyond file size:
removeViewBoxdeletes theviewBoxattribute when it matches the width and height. WithoutviewBox, an SVG can no longer scale to fit its container — it renders at a fixed size. This is the single most common way SVGO breaks production UIs.- Plugins that strip
idattributes can break styling hooks, fragment references (url(#gradient)), and internal links. - Removing
<title>elements hurts accessibility for screen readers. - Precision-reducing plugins like
cleanupNumericValuesround coordinates. At 24px an icon may look identical; scaled up to 200px, rounded path data can produce visible artifacts.
None of this makes SVGO bad — it makes unreviewed defaults bad. The fix is a config file, not a fork.
A baseline config you can defend in review
Create svgo.config.mjs in your project root (the .mjs extension matters — see the version note below):
export default {
multipass: true,
plugins: [
{
name: 'preset-default',
params: {
overrides: {
// Keep viewBox so icons scale responsively.
removeViewBox: false,
// Tune rounding if icons distort at large sizes.
cleanupNumericValues: { floatPrecision: 2 },
},
},
},
],
};
The overrides object is the key mechanism: you keep the whole preset but disable or reparameterize individual plugins by name. Setting a plugin to false removes it from the pipeline; passing an object replaces its default parameters.
Run it from the project root with npx svgo -f assets/icons -o dist/icons to process a folder, or npx svgo icon.svg for a single file. SVGO picks up the config automatically from the working directory. No special permissions are needed — it only reads and writes the files you point it at. The meaningful placeholders here are your input and output paths; note that -o with a single file overwrites or writes to the path you give, so test against copies until you trust the output.
Multipass: cheap wins, real cost
One pass through the pipeline can create new optimization opportunities — collapsing a group might expose redundant attributes that a later plugin can now remove. The multipass: true option reruns the full pipeline until a pass produces no further size reduction (capped internally, so it terminates).
The trade-off is straightforward: you typically gain a few extra percent of savings, and you pay roughly linear CPU time per additional pass. For a handful of icons in a local build, enable it and forget it. For a CI job optimizing hundreds of SVGs on every commit, measure it. If multipass adds minutes to your pipeline for single-digit kilobytes across the whole set, run it only on changed files or accept single-pass output.
You can quantify the difference yourself: optimize the same file with multipass on and off, compare byte sizes, and time both runs. That thirty-second experiment beats any general advice, including this paragraph.
The v2-to-v3 gotcha that wastes afternoons
SVGO v3 moved to ESM-only packaging and tightened the plugin API. Config examples written for v2 — CommonJS module.exports, older plugin names, different override shapes — will fail or silently misbehave under v3. Before copying any config from a blog post (including this one), check your installed version with npx svgo --version and confirm which major line your project is on. If you're on v2 and can't upgrade, the overrides concept still applies, but verify the exact config shape against v3-era assumptions before trusting it.
Verify before you ship
Optimization is only safe if you check the output, not just the byte count. Three quick checks catch nearly every real-world breakage:
- Diff the attributes. Compare original and optimized files for
viewBox,id, andtitle. Any disappearance you didn't explicitly choose is a bug in your config. - Render at multiple sizes. Open the optimized SVG in a browser at small and large dimensions. Precision artifacts only show up when scaled.
- Test the actual usage. If the SVG is referenced via
<use>, CSS mask, or fragment identifier, exercise that path — that's where stripped IDs surface.
Start with the baseline config above, run it against your real icon set, and diff the results. Add overrides only when you can point at a specific broken rendering. That gives you most of SVGO's compression with a config you can explain line by line in code review — which is the actual bar for anything that rewrites your assets automatically.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.