svgo 4.1.0 vs @svgr/core 8.1.0 on 20 verbose 128×128 SVGs (18,990 B). Warm medians (7 rounds after 1 warm-up): SVGO 34 ms / 12,410 B; SVGR JSX 35 ms / 17,620 B; SVGR+plugin-svgo 63 ms / 12,920 B. Fixture /workspace/bench-svgo-svgr/.
4 min read
I ran 20 verbose SVG icons on this box: SVGO 4.1 median 34 ms and cut the set from 18,990 B to 12,410 B (~35% smaller). SVGR 8.1 turned the same files into React JSX in a median 35 ms (17,620 B of components).
Short answer: use SVGO when you ship static .svg assets. Use SVGR when the icons must be React components. Add @svgr/plugin-svgo when you want both (63 ms median here). No affiliate links in this post.
| Path | Median wall | Min / Max | Output bytes |
|---|---|---|---|
| SVGO 4.1 multipass (.svg) | 34 ms | 30 / 43 | 12,410 B |
| SVGR 8.1 JSX only | 35 ms | 33 / 40 | 17,620 B |
| SVGR + plugin-svgo | 63 ms | 56 / 75 | 12,920 B |
Source total was 18,990 B across 20 files. SVGO removed about 35% of the bytes and kept .svg on disk. SVGR trades file size for importable components. For asset-weight budgets after icons land in a bundle, see size-limit vs bundlesize and source-map-explorer vs webpack-bundle-analyzer.
Pick SVGR when icons need props, theming, or tree-shaken imports inside React. The plain JSX path here finished in 35 ms median — fine for a 20-icon set. Turn on @svgr/plugin-svgo if you care about the JSX byte count; that path cost 63 ms and landed near the SVGO-optimized size.
Stay on SVGO alone for static public/ icons, email HTML, or any pipeline that never imports SVG as a component. Pairing SVGO with a CSS toolchain decision is closer to Lightning CSS vs PostCSS than to a React transform.
| Situation | My pick |
|---|---|
| Static .svg in public/ or CMS uploads | SVGO |
| React icon components with props | SVGR (+ plugin-svgo) |
| Both static assets and React imports | SVGO for assets; SVGR for components |
| Huge icon monorepos in CI | Measure; this lab was only 20 files |
| Raster thumbnails, not vectors | See Sharp vs Jimp instead |
| Vite app watching total asset weight | Pair with Vite vs Parcel |
Bottom line: on 20 verbose icons, SVGO returned in 34 ms median and cut ~35% of the bytes; SVGR made JSX in 35 ms; SVGR+SVGO needed 63 ms. Who should not switch: teams already happy with an SVG sprite sheet and no React icon imports.
How this was made: I generated 20 noisy SVG fixtures on the ShopperCove box, installed svgo and @svgr/core with the JSX and SVGO plugins, timed interleaved 20-file passes after a warm-up, recorded JSON medians and output bytes; the write-up was drafted with AI help and checked against that output.
Lab evidence
Hands-on on ShopperCove box 6 Oct 2026 ~00:22–00:23 IST (8 vCPU Intel Xeon / 15 GB shared Linux, Node 22.20.0). svgo 4.1.0 vs @svgr/core 8.1.0 (+ @svgr/plugin-jsx, @svgr/plugin-svgo). Fixture /workspace/bench-svgo-svgr/: 20 verbose 128×128 SVGs, 18,990 B total; (1) SVGO multipass, (2) SVGR JSX only, (3) SVGR+plugin-svgo. 1 warm-up + 7 interleaved rounds. Medians SVGO 34 ms (30–43) / 12,410 B; SVGR 35 ms (33–40) / 17,620 B; SVGR+SVGO 63 ms (56–75) / 12,920 B. Raw: /workspace/bench-svgo-svgr/res-svgo-svgr.json. Not tested: loaders, TS output, sprites, React Native, animated SVG, Windows/macOS. No affiliate.
Hands-on6 Oct 20265 min read
Yarn 4.9.2 (Berry PnP + node-modules linker) vs pnpm 12.9.1 on one 22-dependency React+Vite app. Isolated caches (Yarn enableGlobalCache false; pnpm --store-dir). Warm medians (5): Yarn PnP 1.307 s, Yarn nm 2.471 s, pnpm 0.240 s. Lock+empty medians 1.656 / 2.789 / 0.725 s. Clean disk: Yarn PnP 246 MB; Yarn nm modules 189 MB; pnpm hardlink project 167 MB. Fixture /workspace/bench-yarn-pnpm/. Distinct from pnpm-vs-npm-vs-bun (no Yarn).
Hands-on6 Oct 20264 min read
Lightning CSS 1.33.0 vs PostCSS 8.5.29 + Autoprefixer 10.6.1 + cssnano 6.1.2 on a 76,864-byte CSS fixture: medians 3.16 ms vs 105.75 ms (~33x). Outputs 70498 vs 68474 B. Leave Autoprefixer+cssnano-only stacks for Lightning; keep PostCSS for plugins Lightning does not replace.
Summary5 Oct 20264 min read