About
SVG files exported from tools like Figma, Illustrator, and Inkscape often include metadata, redundant attributes, unnecessary wrapper structure, and verbose path data.
SVGO has been the standard SVG optimizer for years. svgm takes a different approach: a native Rust optimizer designed around fixed-point convergence, safe defaults, and a modern CLI. Like oxlint for ESLint, svgm targets the same problem with a different architecture.
Fixed-point convergence
In some optimizers, additional runs can still reduce output further because later passes create opportunities for earlier ones. svgm is designed to converge in a single invocation by running optimization passes over the in-memory AST until the document stabilizes.
$ svgm icon.svg
icon.svg
13.5 KiB -> 6.6 KiB (51.1% smaller) 0ms 3 passes
No re-parsing between iterations. No manual multipass flag. One invocation, fixed-point optimization.
Install
From source (requires Rust)
Build from repo
# Binary at ./target/release/svgm
Usage
When piped (e.g. svgm icon.svg | gzip), output goes to stdout automatically.
Benchmarks
17 real SVG logos (Figma/Illustrator exports). Same files, same machine.
| svgm | SVGO | |
|---|---|---|
| Compression | 38.4% | 46.9% |
| Total time | 941ms | 2,174ms |
| Speed | 2.3x faster | baseline |
| Invocations to converge | 1 | 1-3 |
FILE SVGM SVGO
anthropic-icon.svg 48.1% 74.8%
apidog.svg 46.8% 55.9%
astro.svg 47.2% 50.9%
claude.svg 49.2% 53.1%
google-play-console.svg 51.1% 60.9%
google-workspace.svg 49.6% 63.1%
incident.svg 49.8% 54.7%
moonshot-ai.svg 55.5% 61.6%
obsidian-icon.svg 32.5% 45.2%
obsidian.svg 48.3% 53.8%
oxc-dark.svg 16.5% 25.5%
oxc-icon.svg 15.7% 25.1%
oxc.svg 16.4% 25.4%
perplexity.svg 52.8% 59.4%
vercel.svg 46.9% 63.5%
vite.svg 15.2% 25.7%
xcode.svg 36.7% 43.8%
svgm is closing the compression gap while staying significantly faster and shipping as a single native binary. Path and transform optimizations are complete; shape-to-path, path merging, and CSS optimizations are still being developed.
How it works
Architecture
parse optimize serialize
SVG string ---------> AST tree ---------> AST tree -----------> SVG string
xmlparser fixed-point minified
loop output
- Parse —
xmlparsertokenizes the SVG into an arena-based AST with parent pointers - Optimize — Run all passes in a loop until no pass reports a change (max 10 iterations)
- Serialize — Write the AST back as a minified SVG string
Passes operate directly on the in-memory AST, avoiding repeated serialize/parse cycles between iterations.
Optimization passes
Removal — strip dead weight
- Comments, doctypes, XML processing instructions
- Editor metadata (Inkscape, Illustrator, Sketch, Figma)
- Empty containers, empty attributes, empty text elements
- Unused namespace declarations
- Attributes matching SVG spec defaults (
opacity="1",stroke="none", etc.)
Normalization — tighten values
- Collapse whitespace in attributes
- Round numeric values, strip trailing zeros and default
pxunits - Shorten colors:
rgb(255,0,0)->red,#aabbcc->#abc
Structural — simplify the tree
- Collapse useless
<g>wrappers (no-attribute groups, single-child groups) - Reference safety: groups with
clip-path,mask, orfilterare never collapsed
Transform — simplify and apply transforms
- Merge consecutive transforms into a single equivalent (
translate(10,20) translate(5,5)->translate(15,25)) - Remove identity transforms (
scale(1),translate(0,0),rotate(0)) - Apply pure translates directly to element coordinates and path data
- Push transforms from single-child groups to child, enabling group collapse
Geometry — compress path data
- Absolute-to-relative coordinate conversion where shorter
LtoH/Vshortcut commandsCtoSandQtoTshorthand curves (reflected control points)- Degenerate curve to line simplification (collinear control points)
- Redundant command removal (zero-length lines)
- Strip leading zeros (
.5instead of0.5) - Implicit command repetition
- Minimal separator insertion
Safety
svgm is conservative by default:
<desc>and<title>are preserved (accessibility semantics)<symbol>and<defs>withidattributes are never removed (may be referenced)- Animation elements (
<animate>,<animateTransform>, etc.) are fully preserved <foreignObject>content is never touchedfill="black"on<svg>is kept (inherited by children)
Rust API
use optimize;
let result = optimize.unwrap;
println!; // optimized SVG string
println!; // convergence iterations
Project structure
svgm/
├── crates/
│ ├── svgm-core/ # Parser, AST, optimizer, serializer, passes
│ └── svgm-cli/ # CLI binary (clap + indicatif)
├── LICENSE-MIT
└── LICENSE-APACHE
Roadmap
- Transform merging, application, and push-down (all 3 phases)
- Shape-to-path conversion (rect, circle, ellipse → shorter
<path>) - Path merging (adjacent paths with identical attributes)
- ID cleanup (remove unused, shorten used)
- CSS
<style>minification - Recursive directory processing (
-r) - WASM build for browser usage
- Node.js bindings via napi-rs
Contributing
svgm is early, but already usable. Contributions and real-world SVG edge cases are especially helpful.
If you find an SVG that svgm corrupts or handles worse than expected, please open an issue with the SVG attached.
License
Dual-licensed under MIT and Apache 2.0.