Rendered on the server
Each icon is a tiny <use href> into one hashed sprite. The browser fetches the sprite once and keeps it.
<Icon name="terminal" />
// <svg><use href="/icons/icons.ce28be92.svg#i-terminal"/></svg>v0.4.1 on npm · MIT · Node ^22.20 || >=24.11
Drop in the SVGs your designers export. svgworm writes one content-hashed sprite and a type with every icon name. Logos keep their colors, mono icons follow currentColor, and a typo fails tsc. Render it in Next.js Server Components, Vite or Astro with zero client JS, and no Turbopack plugin.
pnpm add -D svgworm && pnpm add @svgworm/react
pnpm add -D svgworm && pnpm add @svgworm/react
pnpm add -D svgworm
Only using lucide or another icon set? Its React package is fine. svgworm is for the icons you own.
Or paste this into your coding agent
Set up svgworm in this project for my SVG icons. Read https://svgworm.com/llms.txt, detect the framework (Next.js, Vite + React or Astro), follow the matching guide linked there, and run the build to check it works.$ pnpm svgworm
svgworm: build done
icons 23 (17 mono, 6 multicolor)
delivery use 23, img 0, mask 0
output 11.5 KiB
write public/icons/icons.ce28be92.svg
$ pnpm tsc
error TS2322: Type '"termnial"' is not assignable to type 'IconName'.These numbers are from this page's own build: the sprite it loads and the headers it is served with.
From folder to typed icon
A curated demo, not a live build. The file names come from this site's own icons/ folder. The icons in step 3 are drawn from this page's real sprite.
One file per icon, in any folders you like.
icons/
├── check.svg
├── folder-open.svg
├── terminal.svg
└── emoji/
└── snail.svgsvgworm writes a hashed sprite and the icon names as a type. A wrong name fails tsc.
// generated
public/icons/icons.ce28be92.svg
svgworm/icons.d.ts
// svgworm/icons.d.ts
type IconName =
| "arrow-right"
| "book-open"
| "check"
| …<Icon name="terminal" />
<Icon name="termnial" />
// error TS2322: "termnial" is
// not assignable to IconNameEach icon is a <use href> into/icons/icons.ce28be92.svg. No icon code reaches the browser.
Why svgworm
A sprite tool has to decide, per icon, whether to turn its fills intocurrentColor. Do it for the whole folder and logos turn into black blobs; never do it and mono icons ignore dark mode. svgworm decides per icon: on 31,390 icons it agreed with the rendering 99.98% of the time, and it recolored 0 multicolor icons. How it decides.
Everything else stays out of your bundle: SVGR turns every icon into JavaScript, svgworm ships one cached file and a name. How it compares with SVGR and @neodx/svg.
Each icon is a tiny <use href> into one hashed sprite. The browser fetches the sprite once and keeps it.
<Icon name="terminal" />
// <svg><use href="/icons/icons.ce28be92.svg#i-terminal"/></svg>The generated types list every icon. A typo fails tsc, not production.
<Icon name="termnial" />Mono icons follow currentColor, multicolor icons keep their colors. Gradients, masks and filters render in Chromium, Firefox and WebKit.
A CLI that writes static files. They are byte-identical on Linux, macOS and Windows. Turbopack, webpack and Vite need no plugin. svgworm check fails CI when the output is stale.
Built for AI agents too
Everything works without a prompt: flags,--json, stable exit codes, a JSON Schema for the config, and llms.txt. The agent skills install svgworm in a Next.js or an Astro app. They also explain each diagnostic code with its fix.
$ npx skills add jmlweb/svgworm --skill svgworm-nextjs
$ npx skills add jmlweb/svgworm --skill svgworm-astroMove them into one sprite. Paste one of these prompts into your agent.
From SVGR
Migrate this project's SVG icons from SVGR to svgworm. Read https://svgworm.com/llms.txt first and set svgworm up with the guide for this project's framework, with the folder that holds the SVG files SVGR imports as the svgworm source. Replace every SVGR icon import and its component with the svgworm icon component, keeping the same icons. Remove the SVGR loader rules from the bundler config and @svgr/webpack from package.json once nothing uses them. Run the build and the type check to confirm every icon name resolves.From lucide-react
Migrate this project from lucide-react to svgworm. Read https://svgworm.com/llms.txt first and set svgworm up with the guide for this project's framework. Then copy the SVG source of each lucide-react icon the code uses into the icons folder, replace every lucide-react import with the svgworm icon component, and remove lucide-react from package.json once nothing imports it. Run the build and the type check to confirm every icon name still resolves.From react-icons
Migrate this project from react-icons to svgworm. Read https://svgworm.com/llms.txt first and set svgworm up with the guide for this project's framework. Then copy the SVG source of each react-icons icon the code uses into the icons folder, replace every react-icons import with the svgworm icon component, and remove react-icons from package.json once nothing imports it. Run the build and the type check to confirm every icon name still resolves.This page is the demo
/icons/icons.ce28be92.svg, rendered by the generated Icon.astro at build time. It is served with Cache-Control: immutable. Open your network panel: one icon sprite and no JavaScript to render it.
emoji/carrot emoji/mushroom emoji/rock emoji/seedling emoji/snail emoji/worm arrow-rightbook-opencheckcopyfile-codefolder-opengaugegit-branchlayerspackagepauseplayservershield-checkstarterminalzapSame sprite, same <use href>, decided per icon. Pick a text color. The mono folder follows it; the multicolor snail keeps its own colors. No JavaScript, only CSS color.
Get started in Next.js
init writes the config, the predev/prebuild scripts and the component. Then it builds once, so the app runs before you add an icon.
pnpm add -D svgworm
pnpm add @svgworm/react
pnpm svgworm initEach run writes a hashed sprite to public/icons/ and a typed manifest to svgworm/. predev and prebuild run it for you.
icons/
├── home.svg
├── search.svg
└── brand/logo.svg
pnpm svgwormDecorative by default. Pass title orlabel for an accessible name.
<Icon name="home" />
<Icon name="home" title="Home" />Caching, basePath and manual setup are in the Next.js guide. A complete app lives in examples/next-app-router.
Pick your framework
Server Components, zero client JS, Turbopack.
Hashed sprite that follows your base path.
A dependency-free Icon.astro written by init.
One init, Next.js, Vite or Astro, and your icons ship as one cached file.
pnpm add -D svgworm && pnpm add @svgworm/react
pnpm add -D svgworm && pnpm add @svgworm/react
pnpm add -D svgworm