Every few months I hit the same wall: a folder full of .jpg and .png files that need to be WebP before they go anywhere near production. And every few months I catch myself doing the dumbest possible thing — dragging them one by one into some online converter, waiting for the upload, downloading them back, renaming them.
It doesn't scale, and honestly I'm never thrilled about uploading a client's assets to a random SaaS just to save a few kilobytes.
So the workflow below does the whole folder in one command, locally — nothing leaves your machine, no account, originals left untouched. It runs on @assetopt/cli (open source, MIT), a small CLI I've been building that optimizes images and CSS, JS and SVG in one pass.
Why WebP at all
WebP is typically 25–35% smaller than an equivalent JPEG or PNG at matched quality. It's one of the cheapest wins available for page weight — the format is universally supported now, and the only friction left is doing it in bulk without a painful workflow.
Install
npm install -g @assetopt/cli
Or run it once, without installing anything:
npx @assetopt/cli --help
The one-command version
Point assetopt at a folder and it recurses into it. By default it recompresses images in their original format — to turn JPG and PNG into WebP, you say so once in a config file.
Create a .assetoptrc that routes both source formats to WebP:
{
"images": {
"formatMatrix": { "jpeg": "webp", "png": "webp" },
"quality": { "webp": 82 }
},
"output": { "dir": "./optimized" }
}
Then convert the folder:
assetopt optimize ./images
Every .jpg and .png under ./images is written as .webp into ./optimized, mirroring the source tree. Your originals stay exactly where they are.
Preview the savings before touching your disk
This is the part I actually use the most. analyze reports the exact before/after sizes as a dry run — it writes nothing:
assetopt analyze ./images
You get a per-file table and a total. When the numbers look right, swap analyze for optimize.
Tune the quality, fast
WebP quality is a 1–100 dial (default 82). Lower means smaller files and more visible artifacts:
{ "images": { "formatMatrix": { "jpeg": "webp", "png": "webp" }, "quality": { "webp": 75 } } }
Re-run assetopt analyze ./images after each change. There's an incremental cache on by default, so only the files you actually changed get re-encoded — tuning the quality dial is basically instant instead of re-crunching the whole folder each time.
Real numbers
Running the config above on a sample pack (JPG/PNG → WebP):
| Source | Before | After (WebP) | Saved |
|---|---|---|---|
| Large PNG export (opaque photo) | 5.42 MB | 552 KB | −89.8% |
| JPEG photo | 1.74 MB | 1.44 MB | −17.4% |
The PNG loses the most, because a photo saved as PNG is hugely oversized to begin with — WebP is simply the right container for it. A photo already saved as JPEG is closer to optimal, so the win is smaller but still real. You can reproduce these exact figures by downloading the pack and running the commands above.
When WebP isn't the best target
For transparent PNGs (logos, icons), AVIF often keeps the alpha channel at a smaller size than WebP. assetopt can route by content — opaque → WebP, transparent → AVIF — automatically.
That's the whole loop: preview, convert, done — locally, no upload. If you want the full write-up (config reference, wiring it into a build step, the CI quality gate), it lives here:
👉 Convert JPG & PNG to WebP in bulk — the full guide
If you give it a spin I'd genuinely like to hear what savings you get on your own assets — drop a comment.
Top comments (0)