Optimize output size
Get from a 60 KB hero to a 25 KB one without sacrificing fidelity.
SVG output from Domotion is moderately sized by default — a hero card runs 20–60 KB, a multi-frame animation 80–200 KB. If that's good enough for your context, ship it. If you need it smaller, the techniques on this page typically halve the file size.
Always run optimizeSvg()
The single highest-leverage step is one line:
import { optimizeSvg } from "domotion";
const small = optimizeSvg(svg);
This runs SVGO with a structure-preserving plugin set: shorter path
commands, lower decimal precision, collapsed transforms. Typical reduction
is 30–50%, with bigger wins on path-heavy text. See
optimizeSvg() for plugin
details.
Capture only what you'll show
The biggest cost is glyph outlines, and you only pay for glyphs that appear in the captured tree. Two patterns:
- Crop to the visible region. Don't capture a 1200×800 body when the demo is a 600×300 card. Use the bounding box of the card as the viewport.
- Strip irrelevant subtrees. Hide the cookie banner /
nav / footer with
page.evaluate(() => el.remove())before capturing.
Lean into glyph deduplication
Path mode emits each unique glyph once into <defs> and
references it with <use>. So 100 instances of the letter
"e" in body text cost ~one path's worth of bytes, not 100. Two implications:
- Repeating UI patterns (a list of items with similar text) compress well — repeated glyphs share defs.
- Mixing many fonts and weights on one capture costs more than sticking with one or two — different weights are different glyphs.
Prefer crossfade transitions in animated SVGs
An all-crossfade animation hits the merge fast path:
generateAnimatedSvg de-duplicates elements across frames,
emitting stable elements (logo, header, things that don't change) once
with a permanent opacity: 1. Changing elements get a small
visibility timeline. The output is dramatically smaller than the
"emit each frame as a clipped group" fallback used for
push-left and scroll.
If you can express your story as a series of state changes within the same screen, all-crossfade will give you the smallest result.
Skip overlays when state can be captured
Any overlay disables the merge fast path. If the typed text or click state could just be part of the captured HTML for the next frame, it's both smaller and (slightly) cleaner.
Keep frame count modest
Each frame is one capture's worth of glyph outlines. Even with deduplication, going from 4 frames to 12 typically more than doubles the output. Aim for the smallest number of frames that tells the story — 3–5 is usually enough.
Round and reduce
If you're hand-authoring HTML for a demo (not capturing your real
product), round coordinates and avoid sub-pixel positioning where you can.
Per-character x offsets are stored to one decimal place after
optimizeSvg — fewer non-zero decimals compress better.
Compress for transport
SVGs are plain text. Two transport-level wins worth knowing:
- gzip — most CDNs and servers gzip text/svg+xml responses by default. Path data compresses very well; expect another 50–70% reduction on the wire.
- Brotli — even better than gzip when supported, typically another 10–15% on top.
Concretely: a 60 KB SVG often arrives at the browser as ~12 KB over Brotli. Don't over-optimize the source if your transport handles compression.
Measure first
If you're spending time on optimization, instrument it:
const raw = elementTreeToSvg(tree, w, h);
const small = optimizeSvg(raw);
console.log(`raw=${raw.length} optimized=${small.length} ratio=${(small.length/raw.length).toFixed(2)}`);
Then look at the structure of the SVG itself: which glyph defs are
biggest, which gradients are unused, where the byte count concentrates.
SVG is text — grep and an editor get you a long way.
See also
optimizeSvg()- Animation model — explains why all-crossfade is special.