Using SVGO cleanupIDs to Prefix Icon IDs and Prevent Bundle Collisions
SVG icons from design tools carry bloated IDs that collide in bundles. SVGO's cleanupIDs plugin with a prefix strips the noise and keeps references unique.
28 Dec 2025, 10:15 UTC

Why icon IDs collide in bundles
SVG identifiers are document-scoped. When you inline two icons on the same page, or concatenate them into a sprite, any duplicate id becomes invalid and references resolve to the first match. Design tools optimize for editing history, not production delivery, so they emit verbose IDs such as id0, Rectangle-2-copy, or long hashes. Gradients, clip paths, masks, and <use> elements all point at these IDs. The result is a bundle that is larger than necessary and fragile when combined.
How cleanupIDs normalizes identifiers
SVGO's cleanupIDs plugin scans an SVG for id attributes, removes unreferenced ones, shortens the rest, and can prepend a prefix so the remaining names stay unique across files. In SVGO v2 and later the plugin ships inside the default preset, but default plugin sets have changed between major versions. An explicit configuration is safer than relying on implicit defaults.
| Option | Purpose |
|---|---|
prefix | String prepended to every minified ID (for example, icon-). |
keep | Array of IDs to leave untouched, useful for external references. |
minify | Shorten remaining IDs to the smallest safe form. |
remove | Delete IDs that are not referenced anywhere in the file. |
A typical svgo.config.js for SVGO v2+ overrides the default preset:
// svgo.config.js
module.exports = {
plugins: [
{
name: 'preset-default',
params: {
overrides: {
cleanupIDs: {
prefix: 'icon-',
keep: ['logo-mark'],
},
},
},
},
],
};
If you are pinned to SVGO v1.x, the configuration object shape differs; consult the migration notes for your installed version.
Worked example: renaming IDs in an icon directory
Suppose an exported icon contains a gradient referenced by url(#id1). Running SVGO from the project root with the config above rewrites the identifier. The command below requires Node.js and npx; it modifies files in place, so run it against a version-controlled directory or a copy.
# Run from your project root
npx svgo -f src/icons --config svgo.config.js
Before optimization, the file might contain:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<defs>
<linearGradient id="id1">
<stop offset="0%" stop-color="#fff"/>
<stop offset="100%" stop-color="#000"/>
</linearGradient>
</defs>
<rect width="24" height="24" fill="url(#id1)"/>
</svg>
After optimization, the same file is illustrative and not a measured result:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<defs>
<linearGradient id="icon-a">
<stop offset="0%" stop-color="#fff"/>
<stop offset="100%" stop-color="#000"/>
</linearGradient>
</defs>
<rect width="24" height="24" fill="url(#icon-a)"/>
</svg>
The unused id0 metadata that design tools often include is removed, and the active reference is shortened and prefixed. The practical check is to open the optimized file in a browser and confirm the gradient still renders.
The trade-off: external references and CSS
cleanupIDs assumes IDs are internal to the file. If another document references an icon with <use xlink:href="icons.svg#icon-star">, or if your stylesheet targets #id1, renaming breaks the link. The same applies to accessibility attributes such as aria-labelledby that point at an ID by name.
Preserve those identifiers by adding them to the keep array, or by excluding the specific file from the prefix pass. If you control both sides of the reference, update the consuming code to match the new prefixed names. Never apply cleanupIDs to SVGs that rely on external ID references without explicitly preserving the IDs.
Verifying the result
Optimization is only useful if the output is correct and measurably smaller. Use these checks in your pipeline:
- Side-by-side diff: compare the original and optimized files to confirm only IDs and metadata changed.
- Size assertion: run
wc -cor a tool such asgzip-sizeon the output directory and fail CI if the reduction falls below your threshold. For example, in a CI script you might rungzip-size src/icons/*.svgand compare against a stored baseline. - DOM test: render each icon in a unit test and query
document.getElementById('icon-a')(or the expected prefixed name) to ensure the ID survived. - Manual spot-check: open a sprite or page that uses
<use>in a browser and confirm shapes and ARIA labels remain readable.
Putting it in the build
Wire the config into your bundler—Webpack with svgo-loader, Rollup with rollup-plugin-svgo, or a Gulp task—so every icon passes through cleanupIDs with the same prefix. Keep IDs that cross file boundaries in the keep list, and automate the size and DOM checks above. If an optimization breaks a reference, restore the affected files from version control and adjust the keep list rather than disabling the plugin globally.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.