How to automate svg optimisation in your ci/cd pipeline
- Step 1Pick runner vs CLI — Use the JAD runner when you want JAD's exact minify behaviour with files staying local. Use the SVGO CLI when you want a pure-OSS pipeline and accept SVGO's more aggressive (configurable) plugins. You can run both — JAD for the safe pass, SVGO for extra crunching with review.
- Step 2Discover the tool schema — Call
GET /api/v1/tools/svg-pro-minifier. The response describes the tool as runner-backed and lists its options — which for the minifier is an empty set. Your automation needs nooptionsobject; it just sends the SVG bytes. - Step 3Set up the runner in CI — Install and pair the @jadapps/runner on the build agent (or run it on a self-hosted runner). It binds a loopback HTTP server on
127.0.0.1:9789(legacy49217as fallback); health isGET /v1/healthand jobs go toPOST /v1/tools/<slug>/rununder the/v1/toolsbase. - Step 4Submit each changed SVG — For every changed
.svg(usegit diff --name-onlyagainst the base ref), POST the file to the runner's/v1/tools/svg-pro-minifier/runendpoint. The runner saves the minified output locally and returns its path, size, and metrics — nooptionspayload needed. - Step 5Commit back or assert clean — Two patterns: (a) auto-commit the minified files back to the PR branch; or (b) run the minifier and fail the job if the output differs from the committed file — forcing the developer to commit optimised assets. Idempotence makes (b) reliable: a correctly-minified file produces zero diff.
- Step 6Gate on a size budget — After minifying, run each asset through svg-compression-estimator and fail the build if the gzip size exceeds a threshold you set per directory (e.g. icons must be < 2 KB gzipped). This turns asset bloat into a blocking CI error.
Two automation paths compared
Both run without uploading files to JAD. Pick based on whether you want JAD's exact safe pass or the full (configurable, riskier) SVGO plugin set.
| Aspect | JAD runner path | SVGO CLI path |
|---|---|---|
| Engine | JAD minifier (fixed safe pass) | SVGO (configurable plugins) |
| Where files live | Your machine/runner — in-process, no upload | Your machine/runner (OSS binary) |
| Config surface | None (no options) | svgo.config.js (dozens of plugins) |
| Rendering-safe by default | Yes (no geometry-altering transforms) | Mostly, but some defaults can alter output |
| Coordinate rounding | No (use svg-precision-tuner step) | Yes (cleanupNumericValues) |
| Idempotent output | Yes | Yes if config is pinned |
| Install | npm i + pair @jadapps/runner | npm i -D svgo |
Runner HTTP surface (loopback only)
The @jadapps/runner exposes a tiny HTTP server bound to localhost. These are the endpoints your CI script talks to. The base run path is /v1/tools.
| Purpose | Method + path | Notes |
|---|---|---|
| Default base URL | http://127.0.0.1:9789 | Override via NEXT_PUBLIC_RUNNER_URL; legacy fallback 49217 |
| Health probe | GET /v1/health | Confirm the runner is up before submitting jobs |
| Run a tool | POST /v1/tools/svg-pro-minifier/run | Body carries the SVG; no options needed for the minifier |
| Schema (from JAD API) | GET /api/v1/tools/svg-pro-minifier | Returns runner-backed execution info + empty option set |
Size-budget gate matrix
Example per-directory gzip budgets you can enforce after minifying, using svg-compression-estimator output. Tune to your asset classes.
| Asset class | Suggested gzip budget | Action on breach |
|---|---|---|
| UI/line icons | < 1 KB | Fail build |
| Logos / brand marks | < 4 KB | Fail build |
| Illustrations | < 20 KB | Warn, then fail above 40 KB |
| Auto-traced / generated | Review manually | Block; likely needs re-export, not minify |
Cookbook
Drop-in CI snippets. Replace the base ref and runner host as needed. The minifier needs no options object — these intentionally omit it.
GitHub Actions: SVGO CLI on changed files
Pure-OSS path. Minify only the SVGs changed in the PR, then fail if anything wasn't already optimised.
name: svg-optimize
on: pull_request
jobs:
svgo:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm i -D svgo
- name: Minify changed SVGs
run: |
git diff --name-only origin/${{ github.base_ref }}... -- '*.svg' \
| xargs -r npx svgo --multipass
- name: Fail if assets were not pre-optimized
run: git diff --exit-code -- '*.svg'JAD runner: submit one SVG via curl
Runner path. Probe health, then POST the file to the /v1/tools run endpoint. No options field is sent because the minifier has none.
# 1) confirm the local runner is up curl -fsS http://127.0.0.1:9789/v1/health # 2) minify a file (files stay on this machine) curl -fsS -X POST \ http://127.0.0.1:9789/v1/tools/svg-pro-minifier/run \ -F 'file=@assets/logo.svg' # response includes outputPath, sizeBytes, and metrics # (Size reduction / Original / Minified)
Read the schema before running
Confirm the tool is runner-backed and has no options, so your payload builder stays correct even if the tool evolves.
$ curl -fsS https://jadapps.com/api/v1/tools/svg-pro-minifier | jq '{slug, options, execution}'
{
"slug": "svg-pro-minifier",
"options": [], # nothing to configure
"execution": {
"runnerBacked": true,
"runnerMode": "engine" # runs in-process locally
}
}Pre-commit hook (husky) to keep diffs clean
Run the minifier locally before each commit so optimised, byte-stable assets land in history. Idempotence means a clean file re-stages with no change.
# .husky/pre-commit
staged=$(git diff --cached --name-only -- '*.svg')
[ -z "$staged" ] && exit 0
for f in $staged; do
curl -fsS -X POST http://127.0.0.1:9789/v1/tools/svg-pro-minifier/run \
-F "file=@$f" -o "$f.min"
mv "$f.min" "$f"
git add "$f"
doneTwo-stage pipeline: minify then size-budget gate
Combine the safe minify pass with a gzip budget check. Estimator output gives the real wire size to assert against.
# stage 1: safe minify (no options)
curl -fsS -X POST $RUNNER/v1/tools/svg-pro-minifier/run \
-F 'file=@icons/cart.svg' -o cart.min.svg
# stage 2: gzip budget gate
gzip=$(curl -fsS -X POST $RUNNER/v1/tools/svg-compression-estimator/run \
-F 'file=@cart.min.svg' | grep -oE 'Gzip:[^—]+')
echo "$gzip"
# parse bytes and: [ bytes -lt 1024 ] || { echo 'over budget'; exit 1; }Edge cases and what actually happens
Runner not running in CI
Connection refusedIf the @jadapps/runner isn't started/paired on the agent, GET /v1/health returns connection refused. Add a step to launch and health-check the runner before submitting jobs, or fall back to the SVGO CLI path. The default port is 9789 (legacy 49217); set NEXT_PUBLIC_RUNNER_URL if you bound a custom port.
Sending an options payload
400 rejectedThe minifier validates options against an empty schema. Sending unexpected option keys is invalid — keep the payload to just the file. If you need rounding or simplification, that's a separate tool/step, not an option on this one.
Asset over the tier byte limit
413 too largePer-run limits apply (free 5 MB, Pro 50 MB, higher 2 GB). A huge SVG almost always means an embedded base64 raster — minify can't shrink raster bytes. Re-export as true vector, or handle that asset outside the SVG pipeline.
`git diff --exit-code` flags churn
Build fail (intended)The clean-diff pattern fails the build when an asset wasn't pre-optimised. That's the goal. If it fails unexpectedly on an already-minified file, your local tool version differs from CI — pin the same engine/SVGO version so output is identical.
Mixing SVGO and JAD in one repo
SupportedYou can run JAD's safe minify and SVGO's aggressive plugins, but pick one as the canonical formatter to avoid back-and-forth churn. A common split: JAD/SVGO for the committed safe form, plus a separate review step for any geometry-altering SVGO plugins.
Auto-commit loop on PRs
Watch outAn auto-commit-back step can retrigger the workflow. Guard it (skip when the actor is the bot, or use [skip ci] in the commit message) so you don't loop. Idempotence helps: once assets are minified, the bot's commit is empty and the loop stops.
Invalid SVG committed
InvalidIf a committed file isn't well-formed SVG, the runner/CLI errors on it. Add a parse/validate step (or rely on the tool's Invalid SVG rejection) and surface the filename so the author can fix the malformed source.
viewBox-dependent responsive icons
PreservedUnlike an aggressive SVGO config with removeViewBox enabled, the JAD minifier never strips the viewBox — so responsive icons keep scaling. If you also run SVGO in the pipeline, ensure removeViewBox stays off in your config.
Frequently asked questions
Do my SVGs get uploaded to JAD when I automate this?
Not on the runner path. The @jadapps/runner executes the minify transform in-process on your own machine/CI agent and saves output locally — files never reach JAD's servers. The JAD API endpoint GET /api/v1/tools/svg-pro-minifier only returns schema/execution info, not your file data.
What options does the API expect for svg-pro-minifier?
None. The tool's option schema is empty, so your job payload is just the SVG bytes — no options object. This keeps automation simple and means the payload never breaks when the tool evolves. Confirm by inspecting GET /api/v1/tools/svg-pro-minifier.
What's the runner's endpoint and port?
It binds loopback at http://127.0.0.1:9789 by default (legacy 49217 as a fallback). Health is GET /v1/health; the tool-run base is /v1/tools, so you POST to /v1/tools/svg-pro-minifier/run. Override the host with NEXT_PUBLIC_RUNNER_URL for a custom build.
Can I just use the SVGO CLI instead?
Yes. npx svgo --multipass file.svg in a workflow step is a fine pure-OSS pipeline. The trade-off: SVGO's defaults are more aggressive (and configurable) than JAD's fixed safe pass, so review the output and keep risky plugins like removeViewBox disabled.
Why is idempotence important for CI?
Because it lets you use the git diff --exit-code pattern: minify in CI and fail if the output differs from the committed file. Since the JAD minifier is deterministic with no options, a correctly-optimised file produces zero diff, so the gate is reliable and never flaky.
How do I enforce an SVG size budget?
Chain svg-compression-estimator after the minify step to get the real gzip/brotli wire size, then fail the build if it exceeds your per-directory threshold. Raw minified bytes alone understate savings because servers compress SVG in transit.
Can I round coordinates as part of the same pipeline?
Not within the minifier — it doesn't round numbers. Add a separate svg-precision-tuner step (0–4 decimals) before or after minify. Two decimals is the typical icon sweet spot and is usually the largest single path-data saving.
Will automation strip my accessibility text?
No. The minifier keeps <title> and <desc>. If a CI policy requires removing metadata, add an explicit svg-metadata-scrubber step so the removal is intentional and visible in your workflow, not a silent default.
How do I detect which SVGs changed in a PR?
git diff --name-only origin/<base_ref>... -- '*.svg' lists the changed SVGs; pipe them to your minify step. This keeps CI fast by only touching the files in the PR rather than re-processing the whole repo each run.
Does the auto-commit-back pattern risk an infinite loop?
It can, if the bot's commit retriggers the same workflow. Guard against it by skipping when the triggering actor is your bot or by adding [skip ci] to the commit. Because the minifier is idempotent, once assets are optimised the bot's commit is empty and the cycle ends.
What's the largest SVG the pipeline can handle?
Per-run limits are 5 MB (free), 50 MB (Pro), and 2 GB (higher tiers). On a runner the practical ceiling is process memory. SVGs this large are nearly always embedded raster blobs — minify won't help; fix the source export instead.
Can I run this on a self-hosted GitHub Actions runner?
Yes. Install and pair the @jadapps/runner on the self-hosted box, start it before the job, and point your script at its loopback URL. Self-hosted runners are ideal because the files and the runner share the same machine, so nothing leaves your network.
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.