How to automate svg sprite building to reduce http requests
- Step 1Choose your automation layer — For most apps, a bundler plugin (vite-plugin-svg-icons for Vite, svg-sprite-loader for Webpack) is the path of least resistance — it watches an icon folder and injects a runtime sprite. For language-agnostic CI or non-bundler pipelines, drive JAD's Sprite Builder through the runner.
- Step 2Vite: install and configure the icon plugin —
npm i -D vite-plugin-svg-icons. Invite.config.ts:createSvgIconsPlugin({ iconDirs: [path.resolve(process.cwd(), 'src/icons')], symbolId: 'icon-[name]' }). The[name]token mirrors JAD's browser scheme (ID from file name). Import the plugin's virtual module in your entry so icons register at startup. - Step 3Webpack: wire svg-sprite-loader — Add
svg-sprite-loaderfor.svgtest paths and import icons so the loader collects them into a runtime sprite. ConfiguresymbolId: 'icon-[name]'to match the file-name ID convention used elsewhere in your stack. - Step 4Or: dispatch JAD's builder from the runner —
GET /api/v1/tools/svg-sprite-builderfor the schema. Pair @jadapps/runner once, then POST{ files / text, options: { spritePrefix, additionalSvgs } }tohttp://127.0.0.1:9789/v1/tools/svg-sprite-builder/run. Remember the runner numbers symbolsicon-1,icon-2in source order — capture that order in your script. - Step 5Add a content hash for cache-busting — In Vite,
build.rollupOptions.output.assetFileNameswith a[hash]token gives the emitted sprite a unique name per build. For a runner-builtsprite.svg, hash the file in your CI step and rename it (sprite.<hash>.svg) so the URL changes only when content does. - Step 6Generate icon-name types and reference safely — Write a small script that reads the icon folder and emits a
type IconName = 'cart' | 'search' | …union. Type your<Icon name>component against it so a wrong<use>reference is a compile error, not a silently-blank icon at runtime.
Automation paths compared
What each layer is, who maintains it, and how symbol IDs are formed.
| Path | Who maintains it | Icon discovery | Symbol ID scheme |
|---|---|---|---|
| vite-plugin-svg-icons | Third-party (Vite community) | Globs iconDirs, HMR on change | Configurable symbolId, typically icon-[name] (file name) |
| svg-sprite-loader (Webpack) | Third-party (Webpack community) | Imports collected at build | Configurable symbolId, file-name based |
| Next.js public-folder sprite | You (script or plugin) | Your build script globs + writes public/sprite.svg | Whatever your generator emits |
| JAD Sprite Builder via runner | JAD (engine) + you (CI glue) | You pass the files / additionalSvgs | Sequential prefix-1, prefix-2 (source order) |
| JAD Sprite Builder (browser) | JAD | You select files manually | prefix-<filename-stem> |
JAD Sprite Builder option schema (for runner/MCP payloads)
From GET /api/v1/tools/svg-sprite-builder. These are the only options; there is no per-symbol rename or reorder field.
| Option | Type | Default | Effect |
|---|---|---|---|
spritePrefix | string | icon | Prefix for each <symbol id>. Runner numbers symbols prefix-1, prefix-2, … |
additionalSvgs | string-array | [] | Extra SVG source strings combined after the primary input, each its own <symbol> in order |
Cookbook
Concrete configs for each automation path. The bundler plugins are third-party; the runner path is JAD's.
Vite: auto-sprite an icon directory
vite-plugin-svg-icons globs src/icons and builds a runtime sprite. New files join automatically; symbolId mirrors JAD's filename scheme.
// vite.config.ts
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import path from 'node:path'
export default defineConfig({
plugins: [
createSvgIconsPlugin({
iconDirs: [path.resolve(process.cwd(), 'src/icons')],
symbolId: 'icon-[name]',
}),
],
})
// main.ts
import 'virtual:svg-icons-register' // injects the sprite at startupDispatch JAD's builder from CI via the runner
The public /run endpoint is upload-free and returns pairing instructions. The runner accepts the same payload on localhost and executes locally. Note the sequential ID scheme.
# 1. Discover the schema (no upload)
curl -s https://jadapps.com/api/v1/tools/svg-sprite-builder
# 2. POST to the paired local runner
curl -s -X POST http://127.0.0.1:9789/v1/tools/svg-sprite-builder/run \
-H 'content-type: application/json' \
-d '{
"text": "<svg viewBox=\"0 0 24 24\">…cart…</svg>",
"options": {
"spritePrefix": "icon",
"additionalSvgs": ["<svg viewBox=\"0 0 24 24\">…search…</svg>"]
}
}'
# Output symbols: icon-1 (cart), icon-2 (search) ← sequential, by orderNext.js App Router: external sprite in public/
Write the sprite to public/ during build, reference it by absolute URL. For currentColor styling across the boundary you must inline instead (next example).
// scripts/build-sprite.mjs (run in prebuild)
// glob src/icons/*.svg → combine → write public/sprite.svg
// component
export function Icon({ name, ...p }) {
return (
<svg width={24} height={24} aria-hidden {...p}>
<use href={`/sprite.svg#icon-${name}`} />
</svg>
)
}Inline the sprite for CSS fill control (Next.js)
External-sprite symbols don't inherit your page CSS. To style fills, inline the sprite once server-side in the layout.
// app/layout.tsx
import { readFileSync } from 'node:fs'
const sprite = readFileSync('public/sprite.svg', 'utf8')
export default function RootLayout({ children }) {
return (
<html><body>
<div hidden dangerouslySetInnerHTML={{ __html: sprite }} />
{children}
</body></html>
)
}
// now <use href="#icon-cart"/> resolves inline; currentColor worksGenerate a typed IconName union
Read the icon folder and emit a union so a wrong reference is a compile error. Keeps the build honest as icons come and go.
// scripts/generate-icon-types.mjs
import { readdirSync, writeFileSync } from 'node:fs'
const names = readdirSync('src/icons')
.filter(f => f.endsWith('.svg'))
.map(f => f.replace(/\.svg$/, ''))
writeFileSync('src/icon-names.d.ts',
`export type IconName = ${names.map(n => `'${n}'`).join(' | ')}\n`)
// usage: <Icon name="cart" /> // 'crat' → type errorEdge cases and what actually happens
Runner IDs don't match your filename-based references
Scheme mismatchThe browser tool makes icon-cart from cart.svg, but the runner/API numbers symbols icon-1, icon-2 by source order. If your CI swaps the browser tool for the runner, every <use href="#icon-cart"> breaks. Standardise on one scheme: keep references positional for the runner, or use a bundler plugin (file-name IDs) if you need name-based references.
Expecting the public API to combine uploaded files
400 Runner requiredPOSTing files to https://jadapps.com/api/v1/tools/svg-sprite-builder/run returns HTTP 400 with Runner required and pairing instructions — by design, the public API never accepts uploads. Pair @jadapps/runner and POST to 127.0.0.1:9789 instead. CI scripts must target the local runner endpoint.
Internal IDs collide across globbed icons
ID clashAuto-globbing a directory increases the chance two icons define the same internal id (gradient, clipPath). JAD's builder does NOT namespace these. Add an SVGO prefixIds step in your pipeline before combining, or keep icons single-colour (currentColor) so they have no internal IDs to clash.
HMR doesn't pick up a new icon
Plugin configvite-plugin-svg-icons watches iconDirs. A new file outside that directory, or a path typo, means the watcher misses it. Confirm the icon landed inside a watched iconDir and that symbolId matches your <use> template. This is a plugin behaviour, not a JAD tool behaviour.
External sprite icons ignore your CSS fills
Cross-document scopeSymbols referenced from an external /sprite.svg#id render in a separate scope your page CSS can't reach. Either inline the sprite (so currentColor and CSS apply) or design icons to be self-coloured. Inlining server-side in a layout is the usual fix.
Content hash never changes, cache serves stale icons
Cache miss configIf you set immutable caching but the sprite filename is static (sprite.svg), a deploy won't bust the cache. Emit a content-hashed name (sprite.<hash>.svg) and update references, or skip immutable for a stable filename. The hash must derive from sprite content to be correct.
Type union goes stale after deleting an icon
RegenerateIf generate-icon-types only runs manually, deleting cart.svg leaves 'cart' in the union and a dangling <use href="#icon-cart"> compiles but renders blank. Run the generator as a prebuild/predev step so the union always matches the folder.
Wanting drag-reorder or rename in an automated build
Not a featureNeither JAD's builder nor the bundler plugins offer interactive reorder/rename — order is source/glob order and IDs come from names (browser/plugin) or position (runner). Control order by controlling the input list; control names by renaming source files.
Frequently asked questions
Does JAD's Sprite Builder have a Vite/Webpack plugin?
No first-party bundler plugin. For build-time globbing, use the established third-party plugins (vite-plugin-svg-icons, svg-sprite-loader). To run JAD's exact builder in CI, dispatch it through the @jadapps/runner — pair once and POST the payload to the local runner endpoint.
How do I call the Sprite Builder from a script?
Fetch the schema from GET /api/v1/tools/svg-sprite-builder, then POST { text, options: { spritePrefix, additionalSvgs } } to http://127.0.0.1:9789/v1/tools/svg-sprite-builder/run on a paired runner. The public /run URL itself returns 400 with pairing instructions — it never accepts uploads.
Why does the runner produce icon-1, icon-2 instead of icon-cart?
The runner/API path receives raw SVG strings (primary input plus additionalSvgs), not file names, so it numbers symbols sequentially in source order. The browser tool, which has file names, uses prefix-<filename>. Pick the scheme you want and reference symbols accordingly — don't mix them across environments.
What options does the builder accept?
Two: spritePrefix (string, default icon) and additionalSvgs (array of extra SVG source strings). There is no rename map, no reorder, no inline/external toggle, and no minify option. Run svg-pro-minifier on inputs first if you want smaller symbols.
How do I cache-bust the sprite?
Use a content-hashed filename (sprite.<hash>.svg) and serve it with Cache-Control: public, max-age=31536000, immutable. In Vite, the asset pipeline hashes automatically; for a runner-built file, hash it in CI and rename. The hash must come from the sprite's bytes so the URL changes only on real change.
Can I split icons into multiple sprites for code-splitting?
Yes. Point each build at a different icon directory or use a distinct prefix, producing separate sprite files. Load a critical sprite eagerly and lazy-load secondary ones. JAD's builder produces one sprite per invocation, so run it once per sprite you want.
Does the Vite plugin hot-reload when icons change?
Yes — vite-plugin-svg-icons watches the configured iconDirs and updates the virtual sprite module on change without a full reload. That's a feature of the third-party plugin, not of JAD's tool. JAD's builder is a one-shot transform you re-run.
How do I get compile-time safety for icon names?
Generate a TypeScript union from the icon folder (a few lines of Node reading readdirSync) and type your <Icon name> prop against it. A wrong name then fails type-checking instead of rendering a blank icon. Run the generator as a prebuild step so it stays in sync.
Will auto-globbing cause internal ID collisions?
It can. The builder doesn't namespace internal ids, so two globbed icons sharing id="a" for a gradient will clash. Add an SVGO prefixIds pass before combining, or keep icons single-colour with currentColor so they carry no internal IDs.
Is anything uploaded when I automate this?
No. Whether you use a bundler plugin (runs in your build) or the runner (runs on your machine), no icon content reaches JAD's servers. The public API is explicitly upload-free and returns pairing instructions instead of accepting files.
How many icons can one runner invocation combine?
The runner runs the same engine as the web tool; practical limits are your machine's memory and the tier ceilings (Developer is unlimited batch at up to 2 GB per file). For thousands of icons, minify first and consider splitting into multiple sprites by feature area.
Can I update one symbol without rebuilding the sprite?
Not through the tool — there's no patch endpoint. Re-run the build with the updated icon set. Because the sprite is plain text, a CI step can also regex-replace a single <symbol>…</symbol> block if you prefer surgical updates, but that's your script, not a tool feature.
Privacy first
Every JAD SVG tool runs entirely in your browser using the DOM API and Canvas. Your SVG files never leave your device — verified by zero outbound network requests during processing.