assetopt

Guide

Optimize images before deploying

Shrink your build output right before deploy — recompress images, CSS, JS and SVG in CI without breaking asset references. Works with Vercel, Netlify, any static host.

The safest place to optimize assets is after your bundler runs and before you deploy. At that point the build has already wired up every file path in your HTML and CSS, so as long as you keep file extensions intact, nothing breaks. This guide recompresses a dist/ folder in CI and deploys the smaller output — on Vercel, Netlify, GitHub Pages, or any static host.

Everything runs against @assetopt/cli (open source, MIT).

The one rule: don’t convert formats on built output

This is the difference between this guide and the WebP / AVIF guides.

Those guides convert .jpg.webp, which changes the file extension. That’s fine when you’re preparing source assets and then reference the new names yourself. But your built HTML already points at hero.jpg — rename it to hero.webp and you get silent 404s in production.

So on build output, use assetopt’s default outputFormat: "keep": recompress every asset, keep the same name and extension. Same URLs, fewer bytes.

Install

npm install -g @assetopt/cli

In CI you’ll usually call it with npx instead (no global install needed).

A safe .assetoptrc for build output

{
  "images": {
    "quality": { "jpeg": 82, "png": 80, "webp": 82, "avif": 72 },
    "stripMetadata": true
  },
  "css": { "minify": true },
  "js": { "minify": true },
  "svg": { "multipass": true },
  "output": { "dir": "./optimized" }
}

No preset and no outputFormat — every extension is preserved. (web-perf deliberately converts formats, so it’s the wrong choice here.)

Check it once locally

npm run build
assetopt analyze dist

analyze is a dry run — it reports the savings without writing anything. When the numbers look right, move it into CI.

Wire it into CI

GitHub Actions

- run: npm ci
- run: npm run build
- run: npx assetopt optimize dist --min-savings 5
- run: # your deploy step, pointing at ./optimized/

--min-savings 5 turns assetopt into a quality gate: if total savings drop below 5%, the step exits non-zero and CI fails. Start low (3–5%) and tighten once you have a baseline — a project that already ships optimized assets will fail a high threshold every run.

Persist the cache between runs so unchanged files aren’t re-encoded:

- uses: actions/cache@v4
  with:
    path: ./optimized
    key: assetopt-${{ hashFiles('dist/**') }}

Vercel / Netlify

On a host that builds for you, fold the optimize step into your build command and publish the optimized folder:

  • Build command: npm run build && npx assetopt optimize dist
  • Output / publish directory: optimized

The originals in dist/ are never modified; the deployable copy lives in ./optimized/.

What a run looks like

Illustrative output (recompression only — your numbers depend on how optimized your source already is):

  image  hero.jpg          412.3 KB →  147.8 KB   -64.1%
  css    main.css           48.7 KB →   34.2 KB   -29.7%
  js     app.js            186.5 KB →  112.9 KB   -39.4%
  svg    icon-arrow.svg      1.2 KB →    0.6 KB   -50.0%

  Saved 1.0 MB (61.0%) · 0 cached · 1.8s

Most of the payload win comes from images; CSS/JS/SVG minification adds a steady few percent on top. You can reproduce real per-file numbers on the downloadable sample pack.

Common pitfalls

  • Deploy ./optimized/, not ./dist/. Forgetting this makes the whole pipeline a no-op. A guard like [ -d ./optimized ] || exit 1 in the deploy script catches it.
  • Don’t run optimize on src/ or public/. Those aren’t processed by your build yet; target the build output. See the CI pre-deploy workflow for the full rationale.
  • Never set outputFormat: "webp" on build output — every .jpg becomes .jpg.webp and your HTML 404s silently.

Next steps

Get the CLI

$ npm install -g @assetopt/cli

Open source, MIT. See the docs orstar it on GitHub.