Workflows
CI pre-deploy on dist/
The most natural workflow for any project that already has a build step. You let your bundler (Vite, Next, Astro, anything) produce a dist/ whose HTML and CSS reference resolved URLs, then you run assetopt optimize on that output right before deploying.
Why this works so well: the build has already wired up file paths and references, so as long as you keep file extensions intact, nothing breaks. assetopt’s outputFormat: 'keep' default does exactly that — recompress every asset, keep the same name and extension.
Prerequisites
- A build pipeline that emits a deterministic output directory (commonly
dist/). - Node.js 20+ available in your CI environment.
- Your deploy step pointing at the optimized output (not at
dist/directly).
Recommended .assetoptrc
{
"images": {
"quality": { "jpeg": 82, "png": 80, "webp": 82, "avif": 72 },
"stripMetadata": true
},
"css": { "minify": true },
"js": { "minify": true },
"svg": { "multipass": true },
"output": { "dir": "./optimized" }
}
Do not set "preset": "web-perf" here. The preset converts file extensions (jpeg → webp, png → avif), and your built HTML still references the originals. Format conversion in this workflow is unsafe unless something rewrites the imports in your build — which assetopt deliberately does not do. Stick to recompression with outputFormat: 'keep'.
Step by step
# Local check (optional but recommended once)
npm run build
assetopt analyze dist
Then in CI (GitHub Actions example):
- run: npm ci
- run: npm run build
- run: npx assetopt optimize dist --min-savings 5
- run: # your deploy step, pointing at ./optimized/
The --min-savings 5 flag turns assetopt into a quality gate: if the build’s total savings drop below 5%, CI fails. Start low and tighten over time.
Expected terminal output
✓ config loaded from /workspace/.assetoptrc
Optimizing /workspace/dist...
image hero.jpg 412.3 KB → 147.8 KB -64.1%
image about-team.jpg 1.1 MB → 389.2 KB -65.5%
image logo.png 14.2 KB → 8.1 KB -42.9%
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%
6 files · 1.7 MB → 692.8 KB · Saved 1.0 MB (61.0%) · 0 cached · 1.8s
On subsequent runs, the cache kicks in:
6 files · 1.7 MB → 692.8 KB · Saved 1.0 MB (61.0%) · 6 cached · 0.1s
Common pitfalls
-
Don’t run
optimizeon source folders (src/,public/). Those assets haven’t been processed by your build yet, and your bundled imports usually won’t pick up the optimized copies. Always target the build output. -
Persist the cache between CI runs. The manifest lives in
<output.dir>/.assetopt-cache.json. CI typically wipes the workspace, killing the cache. In GitHub Actions, add a step to cache the./optimized/directory keyed on a hash ofdist/:- uses: actions/cache@v4 with: path: ./optimized key: assetopt-${{ hashFiles('dist/**') }} -
Threshold tuning.
--min-savings 15on a project that already outputs already-optimized assets (modern builds with image-min plugins) will fail every run. Start at 3-5%, raise it once you have a baseline. -
The deploy target must be
./optimized/, not./dist/. If you forget, you deploy unoptimized files and the whole pipeline becomes a no-op. Add a sanity check in your deploy script (e.g.[ -d ./optimized ] || exit 1). -
outputFormat: 'keep'is critical here. If you ever explicitly setoutputFormat: 'webp'(or use theweb-perfpreset), every.jpgbecomes.jpg.webpand your HTML breaks silently — no error, just 404s in production.