Aggressive SVG Optimization with SVGO While Preserving Critical IDs and Data Attributes
Optimize SVG assets with SVGO while preserving critical IDs, classes, and data attributes. Learn how to configure the cleanupIDs and removeAttrs plugins, run multipass optimizations, and integrate the process into CI and bundlers for reliable, size‑efficient icons.
19 Mar 2026, 04:58 UTC

Desired Outcome
Reduce the size of SVG assets for production without stripping identifiers that downstream CSS, JavaScript, or analytics scripts rely on. The goal is to keep id prefixes like icon- or logo-, class names, and specific data-* attributes (e.g., data-tracking) while removing all other unnecessary markup.
Prerequisites
- Node.js 20+ – SVGO v3 is ESM‑only.
- Project uses a bundler that supports loader options (Vite, Webpack, Rollup).
- SVG assets are stored under
src/assets/svgand will be emitted todist/assets/svg. - Optional:
size-limitorbundlesizedev‑dependency for CI checks.
Configuring SVGO
Create an ESM‑compatible config file svgo.config.cjs (or svgo.config.js if you prefer dynamic imports). The following example demonstrates the core plugins and custom rules needed for aggressive optimization while preserving the required attributes.
module.exports = {
multipass: true, // re‑run until no further changes
plugins: [
{
name: "cleanupIDs",
params: {
// Preserve IDs that start with these prefixes
preserve: ["^icon-", "^logo-", "^tracking-"]
}
},
{
name: "removeAttrs",
params: {
// Keep only data-tracking and data-test-id, drop others
attrs: "(data-)(?!tracking-|test-id).*"
}
},
"removeViewBox", // remove only if you control viewport via CSS
{ name: "removeDimensions", params: { preserve: true } },
"collapseGroups",
"convertShapeToPath",
"sortAttrs"
]
};
Key points:
preserveaccepts an array of regular expressions. SVGO matches against the fullidvalue; patterns like^icon-keep any ID that starts with that prefix.- The
removeAttrsregex uses a negative lookahead to drop alldata-*attributes exceptdata-trackinganddata-test-id. - Setting
multipass: trueallows plugins such ascollapseGroupsto unlock further optimizations in subsequent passes. - Do not enable
removeViewBoxunless you are certain the icon will be sized via CSS. The viewBox is essential for responsive scaling.
Example Workflow
- Run SVGO from the CLI to create a baseline:
- Diff the results to ensure no unintended changes:
- Measure size savings with gzipped output:
- Integrate into CI (example for GitHub Actions):
npx svgo --config=svgo.config.cjs --multipass \
--input=src/assets/svg \
--output=tmp/optimized \
--recursive
Use --pretty during debugging to read the output.
diff -r src/assets/svg tmp/optimized || true
Any differences will surface, allowing you to adjust the config before committing.
npx gzip-size-cli tmp/optimized/*.svg > sizes.txt
steps:
- uses: actions/checkout@v4
- name: Install deps
run: npm ci
- name: Optimize SVGs
run: |
npx svgo --config=svgo.config.cjs --multipass \
--input=src/assets/svg \
--output=dist/assets/svg \
--recursive
- name: Verify size budget
run: |
npm i size-limit --save-dev
npx size-limit --config size-limit.config.js
The size-limit step can be configured to fail if the gzipped size exceeds a threshold.
Bundler Integration
When SVGs are inlined by a loader (e.g., vite-plugin-svgr or svgr/webpack), pass the same SVGO config via the loader options so the same rules apply at build time.
// vite.config.js
import svgr from "vite-plugin-svgr";
export default {
plugins: [
svgr({
svgo: true,
svgoConfig: require("./svgo.config.cjs")
})
]
};
For Webpack:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.svg$/,
use: [
{
loader: "@svgr/webpack",
options: {
svgo: true,
svgoConfig: require("./svgo.config.cjs")
}
}
]
}
]
}
};
Verification Steps
- Inspect the DOM after a hot‑reload: React DevTools or Vue DevTools should show the preserved
idanddata-trackingattributes in the rendered SVG. - Run
npx svgo --pretty --show-plugins --config=svgo.config.cjsand confirm thecleanupIDsandremoveAttrsplugins list the expected parameters. - Use
gzip-size-clito compare before/after sizes. A net reduction confirms that the aggressive passes are beneficial. - For a quick sanity check, open a saved SVG in the browser and verify that CSS selectors like
#icon-userstill apply.
Common Pitfalls & Recovery
- Regex mismatch: SVGO matches against the entire
idstring. Ensure your^icon-pattern covers the full value. If an ID likeicon-user-1is still minified, adjust the regex to^icon-(unchanged) or addicon-without anchors. - Over‑optimizing shapes:
convertShapeToPathcan increase file size for simple shapes. Disable it if you observe size regressions. - Removed
viewBox: If the icon no longer scales, re‑enableremoveViewBoxor setpreserve: truefor icons that need it. - CI CPU waste: Running
multipasson already optimized SVGs is idempotent but CPU‑intensive. Gate it with a hash check: only run when source files change. - CommonJS projects: SVGO v3+ is ESM‑only. Use dynamic
import()or installsvgo@2if you cannot migrate.
Conclusion
By tailoring SVGO’s plugin configuration—especially cleanupIDs and removeAttrs—you can aggressively shrink SVGs while keeping the identifiers your application depends on. The multipass pipeline unlocks additional passes, and CI integration ensures regressions are caught early. Use the verification steps above to confirm the output meets both size and functional requirements before deploying to production.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.