Somebody (or some coding agent) adds a screenshot to the docs, and the PNG is 4 MB. Nobody notices in review, because the diff of a binary file is just "changed". A few months later the repo is heavy, the docs page is slow, and the same images are still being downloaded by every clone.
The fix is boring and mechanical, which is exactly why it should be automated: compress images before they are committed, and make CI fail when one slips through. Here is how to do both with a single command that needs no account and uploads nothing.
The one command
The PixelVault CLI has an optimize command that runs entirely on your machine (it needs Node 20.9+):
It resizes, converts and strips metadata, and prints the saving for each file. A few properties make it safe to wire into automation:
- It never writes a file bigger than the original. If an image can't be made smaller, it is left alone and reported as already optimal.
- It is idempotent. Running it again over images it already compressed changes nothing, so a hook or CI check doesn't nibble at the same file every time.
- It refuses to lose data. It stops before writing anything if two inputs would produce the same output file or an output would overwrite an input, and it writes through a temporary file so an interrupted run can't truncate an image.
- Metadata is stripped (camera details, GPS location), after the photo's rotation is applied so it still displays the right way up. Add
--keep-metadataif you need it. - It supports JPG, PNG, WebP and AVIF. GIF, SVG, animated images and iPhone HEIC are skipped. For HEIC, see HEIC to URL.
Useful variations:
The default format is keep, so a PNG stays a PNG (lossless) and a JPEG stays a JPEG. Converting to WebP is usually where the biggest saving is, but it changes the file extension, so update any references to the old name.
Compress staged images in a pre-commit hook
Save this as .git/hooks/pre-commit (or call it from your hook manager) and make it executable. It compresses only the images you are about to commit, then re-stages them:
--in-place overwrites the file with its optimized version, which is what you want for a file that is about to be committed anyway. Do not use it on files that are not under version control: there is no undo.
Fail the build when an image is not optimized
Hooks can be skipped, so add a check in CI too. Because optimize is idempotent, a repo whose images are already compressed produces no changes, and anything it does change is exactly the set of images someone forgot:
Adjust the directories to wherever your images live: the command stops with an error if a path doesn't exist. If you would rather have CI fix the images than fail, commit the result back to the branch instead of running git diff --exit-code.
Make your AI coding agent do it
Coding agents are prolific screenshot-takers, so they are the most likely source of oversized images. Two ways to nudge them:
- Add one line to your
CLAUDE.mdorAGENTS.md: "Before committing images, runnpx pixelvault-cli optimize <files> --in-place." - Install the PixelVault skill (
claude plugin add pixelvault-dev/skill), which includes/pixelvault-optimize. It tells the agent to preview with--dry-runfirst, prefer a separate output unless the files are committed, and report the saving.
Prefer not to install anything?
For a handful of images there is a free in-browser image compressor. Drop your files in, pick a format, quality and maximum width, and download the results. Nothing is uploaded unless you click "Get a link".
When you also want a hosted URL
If a compressed image needs a public URL, for example for a README, an email or a docs page that should not carry the binary at all, add --host to upload the optimized result and print its CDN URL:
That step needs a PixelVault account and API key (see the quickstart). Compressing locally never does.
Want to try it on one image first? Open the free image compressor, or run npx pixelvault-cli optimize --help to see every option.