How to automate image compression in a build pipeline

Any process that depends on a person remembering to optimise an image will fail within a month. Put it in the build and it stops being a discipline.

Where to put it

Framework built-in
Next.js, Nuxt and Astro all optimise images at build or request time.
Build plugin
A Vite or webpack plugin that processes imported images.
Pre-commit hook
Catches oversized files before they enter the repository.
CI check
Fails the build when an image exceeds a budget.
Image CDN
Resizes and reformats on request. No build step at all.
The workhorse
sharp — the Node library nearly all of the above use underneath.

Building the automation

  1. Step 1: Start with whatever your framework provides

    Next.js, Nuxt, Astro and SvelteKit all ship image handling that resizes, converts to modern formats and emits correct markup. Using it well is almost always better than building your own pipeline.

  2. Step 2: Add a budget check that can fail the build

    A short CI script that walks the image directories and fails if anything exceeds your limit. This is what stops a 12 MB hero image being committed at 5pm on a Friday.

  3. Step 3: Catch it earlier with a pre-commit hook

    The same check running locally gives the developer the feedback immediately, rather than after a CI run. Keep it fast, or people will bypass it.

  4. Step 4: Consider an image CDN instead

    A service that resizes and reformats on request removes the build step entirely and handles formats you cannot produce locally. The trade is an external dependency in your delivery path and an ongoing cost.

Approaches compared

sharp, and why everything uses it

Nearly every Node image pipeline is sharp underneath. It wraps libvips, which is fast and uses far less memory than ImageMagick on large files.

import sharp from "sharp";

await sharp("input.jpg")
  .resize({ width: 1600, withoutEnlargement: true })
  .webp({ quality: 78 })
  .toFile("output.webp");

withoutEnlargement is the option people forget: without it, sharp will happily upscale a small image and produce something soft and larger.

Framework built-ins

Next.js, Nuxt Image, Astro's image integration and SvelteKit's enhanced-img all do the same broad job: take a source image, generate several widths, convert to modern formats, and emit an img with correct srcset, sizes, width and height.

That last part matters more than the compression. Getting the markup right prevents layout shift and lets the browser choose sensibly, and it is the part hand-rolled pipelines usually get wrong.

If your framework has this, use it before building anything custom.

A budget check that actually holds

The most valuable automation is often not compression at all — it is a gate that refuses oversized files.

# Fail if any image in public/ exceeds 300 KB
find public -type f \( -name "*.jpg" -o -name "*.png" -o -name "*.webp" \) \
  -size +300k -print -exec false {} + \
  && echo "All images within budget"

Run it in CI and it becomes a rule rather than a request. The failure message should say what to do — resize to N pixels, compress to under X KB — or people will simply raise the limit.

Image CDNs

Cloudflare Images, imgix, Cloudinary and similar services resize and reformat on request, driven by URL parameters, and cache the result.

For: no build step, format negotiation per browser including AVIF, and no need to decide in advance which sizes you will need.

Against: an external dependency in the critical path of every page, an ongoing cost that scales with traffic, and a migration if you leave. For a large or highly variable image catalogue the trade is usually worth it; for a site with fifty images it is not.

What to automate, in priority order

  1. Correct markup. Dimensions, srcset, sizes, lazy loading below the fold. Biggest effect, lowest risk.
  2. Resizing. Generate the widths the layout needs from one master.
  3. Format conversion. WebP with a fallback; AVIF too if your pipeline can.
  4. Compression. A sensible quality applied consistently.
  5. A budget gate. So none of the above quietly stops happening.

Teams usually start at four and skip one, which is backwards — the markup is where the measurable wins are.

Do it here instead

These run in your browser on any device, so nothing is uploaded and there is nothing to install.

  • Bulk compressor

    One setting across a whole folder, processed in parallel, downloaded as a ZIP.

    Open the tool
  • JPG to WebP

    Typically 25–35% smaller at matched quality. The standard web win.

    Open the tool
  • Resize image

    By pixels or percentage, aspect ratio locked, across a whole batch.

    Open the tool
  • Compress to 200KB

    Full resolution kept, quality still invisible to the eye.

    Open the tool

Questions

Frequently asked questions

Every answer below is present in the page source, expanded, so it can be read without opening anything.

Should I use my framework's image component?

Almost always, yes. Next.js, Nuxt, Astro and SvelteKit all handle resizing, format conversion and — more importantly — emit correct srcset, sizes and dimensions. That markup is where most of the measurable benefit is, and it is what hand-rolled pipelines usually get wrong.

What library should I use in a Node build?

sharp. It wraps libvips, is fast, uses far less memory than ImageMagick on large files, and is what most framework integrations use internally. Remember withoutEnlargement, or it will happily upscale small images.

Is an image CDN worth it?

For a large or frequently changing catalogue, usually — you get per-browser format negotiation and no build step. For a site with a few dozen images it is an external dependency and an ongoing cost for something a build step does free.

How do I stop people committing huge images?

A CI check that fails the build above a size budget, ideally mirrored in a pre-commit hook for faster feedback. Make the failure message say what to do, or the limit will just get raised.

Should compression run at build time or on request?

Build time for a fixed set of images — it happens once and costs nothing per visitor. On request when the catalogue is large or user-generated, where pre-generating every variant is impractical.

Can I generate AVIF in my pipeline?

Yes — sharp supports it, as do most framework integrations. Encoding is slow, which is fine at build time and generally not viable on request. This is one of the things a build pipeline can do that a browser tool cannot.

From the blog

Related reading

More background from the blog.

  • Practical Guides

    How to Compress Images for a Website Without Wrecking Them

    Resize before you compress. Measured on one photograph: half the width at quality 80 produced a 16.4 KB file, while full width at quality 40 produced 20.6 KB and looked far worse. The order of operations matters more than the settings.

    8 min read

  • Image Formats

    WebP vs JPEG: Which Should You Use, and When JPEG Still Wins

    WebP produced a 27.0 KB file against MozJPEG's 41.1 KB on the same photograph at slightly higher measured fidelity — 34% smaller. It also produced a larger file than JPEG on random noise. The advantage is real and content-dependent.

    9 min read

  • Image Compression

    JPEG Quality Settings Explained: What the Number Actually Means

    Quality 80 is not 80% of anything. It is an index into a quantisation table. Measured on one photograph: dropping from 100 to 90 removes 77% of the file and 4.2 dB of fidelity; dropping from 40 to 30 removes 17% and costs almost as much.

    8 min read