Type Here to Get Search Results !

How to Build an Automated Image Optimization Workflow for Developers (Step-by-Step Guide)

How to Build an Automated Image Optimization Workflow for Developers (Step-by-Step Guide)

How to Build an Automated Image Optimization Workflow for Developers (Step-by-Step Guide)

📅 Published: May 2, 2026  |  ⏱️ 9 min read  |  🏷️ Category: Developer Tools > Tutorials
Automation Sharp imagemin GitHub Actions CI/CD npm Scripts Tutorial

Introduction: Manual Image Optimization Doesn't Scale

Manual image compression is a trap. You do it once, feel productive, then two weeks later a new batch of Figma exports lands in your repository — uncompressed, mis-formatted, and 4x larger than they need to be.

The only reliable solution is automation: an image optimization pipeline that runs without human intervention and guarantees that every image that ships to production is already optimized.

This step-by-step guide shows you how to build exactly that — using free image optimization tools for developers like Sharp, imagemin, npm scripts, and GitHub Actions. No paid services, no black-box SaaS, no vendor lock-in.

⚠️ Prerequisites: This guide assumes Node.js v18+, familiarity with npm/yarn, and a basic understanding of npm scripts. GitHub Actions steps require a GitHub-hosted repository.
Automated image optimization workflow for developers — pipeline diagram showing commit, compression, build, and deployment stages

Why You Must Automate Your Image Optimization

If you're still compressing images manually before each commit, here's what that workflow actually looks like over time: inconsistent output quality, forgotten files, no clear standards for new team members, and performance regressions that creep in on every sprint.

Automated pipelines solve all of this because they are:

  • Consistent — Every image is processed with identical settings, every time
  • Invisible — Runs as part of your existing workflow with zero additional effort
  • Scalable — Handles 1 image or 1,000 images with the same script
  • Enforceable — Pre-commit hooks and CI checks ensure no unoptimized image can bypass the pipeline
  • Documented — Your quality settings live in version-controlled config, not in someone's memory

Not sure if your images are actually causing a problem? First read: Why Unoptimized Images Are Killing Your Developer Projects.

Step-by-Step: Building Your Automated Image Optimization Pipeline

Step-by-step automated image pipeline for developers — install Sharp, write script, npm script, pre-commit hook, CI/CD GitHub Actions
Step 1 of 5

Install Sharp as a Dev Dependency

Sharp is the fastest Node.js image processing library and the backbone of our pipeline. Install it as a development dependency:

npm install sharp --save-dev

Note: Sharp uses native binaries. If you encounter installation issues on Alpine Linux or ARM-based environments, follow the Sharp installation guide for platform-specific instructions.

Step 2 of 5

Write Your Image Optimization Script

Create a file at scripts/optimize-images.js in your project root:

// scripts/optimize-images.js
const sharp = require('sharp');
const fs = require('fs');
const path = require('path');

// Configuration — adjust to your project
const CONFIG = {
  inputDir: './src/assets/images/original',
  outputDir: './public/images',
  quality: {
    webp: 82,
    jpeg: 85,
    avif: 65,
    png: 9   // PNG compression level 0-9
  },
  maxWidth: 1920  // Resize images wider than this
};

// Create output directory if it doesn't exist
if (!fs.existsSync(CONFIG.outputDir)) {
  fs.mkdirSync(CONFIG.outputDir, { recursive: true });
}

// Get all image files from input directory
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif'];
const files = fs.readdirSync(CONFIG.inputDir)
  .filter(f => imageExtensions.includes(path.extname(f).toLowerCase()));

let processed = 0;
let totalSaved = 0;

Promise.all(files.map(async (file) => {
  const inputPath = path.join(CONFIG.inputDir, file);
  const baseName = path.parse(file).name;
  const outputPath = path.join(CONFIG.outputDir, `${baseName}.webp`);

  const originalSize = fs.statSync(inputPath).size;

  await sharp(inputPath)
    .resize({ width: CONFIG.maxWidth, withoutEnlargement: true })
    .webp({ quality: CONFIG.quality.webp })
    .toFile(outputPath);

  const optimizedSize = fs.statSync(outputPath).size;
  const saved = Math.round((1 - optimizedSize / originalSize) * 100);
  totalSaved += (originalSize - optimizedSize);
  processed++;

  console.log(`✅ ${file} → ${baseName}.webp (${saved}% smaller)`);

})).then(() => {
  const savedKB = Math.round(totalSaved / 1024);
  console.log(`\n🎉 Done! Processed ${processed} images. Total saved: ${savedKB}KB`);
}).catch(err => {
  console.error('❌ Optimization failed:', err);
  process.exit(1);
});
Step 3 of 5

Add an npm Script to package.json

Wire your script into package.json so it can be run manually and as part of your build:

{
  "scripts": {
    "optimize:images": "node scripts/optimize-images.js",
    "prebuild": "npm run optimize:images",
    "build": "your-existing-build-command"
  }
}

Using prebuild ensures images are always optimized before your production build runs — no separate step to remember.

Step 4 of 5

Set Up a Pre-Commit Hook with Husky

Pre-commit hooks run automatically before each git commit, ensuring no unoptimized image ever enters your repository. Install husky and lint-staged:

npm install husky lint-staged --save-dev
npx husky init

Configure lint-staged in package.json:

{
  "lint-staged": {
    "src/assets/images/original/*.{jpg,jpeg,png,gif}": [
      "node scripts/optimize-images.js",
      "git add public/images"
    ]
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  }
}

Now, any time a developer adds an image to the source directory and commits, the optimization script runs automatically before the commit is recorded.

Step 5 of 5

Add a GitHub Actions Workflow for CI/CD

As a final safety net, add a GitHub Actions workflow that runs your optimization script on every push and pull request. Create .github/workflows/optimize-images.yml:

# .github/workflows/optimize-images.yml
name: Optimize Images

on:
  push:
    paths:
      - 'src/assets/images/original/**'
  pull_request:
    paths:
      - 'src/assets/images/original/**'

jobs:
  optimize:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run image optimization
        run: npm run optimize:images

      - name: Commit optimized images
        uses: stefanzweifel/git-auto-commit-action@v5
        with:
          commit_message: 'chore: auto-optimize images [skip ci]'
          file_pattern: 'public/images/**'

This workflow triggers only when image files in your source directory change, keeping CI run times fast and focused.

GitHub Actions workflow running automated image optimization on pull request — CI/CD pipeline for image compression
✅ What You've Built: A complete image optimization pipeline that runs on commit (husky), before builds (prebuild npm script), and on CI (GitHub Actions). Unoptimized images can no longer reach production.

Advanced Tips for Your Image Optimization Pipeline

  • 🚀 Generate multiple sizes for responsive images — Extend your script to output 400px, 800px, and 1200px variants, then use srcset in your HTML
  • 🚀 Add AVIF as a secondary output — Generate both .webp and .avif versions, then serve AVIF to supporting browsers via <picture>
  • 🚀 Cache processed images in CI — Use GitHub Actions cache to skip re-processing images that haven't changed between runs
  • 🚀 Add a size budget check — Fail the CI job if any single image exceeds a defined KB threshold (e.g., 300KB for content images)
  • 🚀 Log a compression report — Output a summary of bytes saved per image to your CI logs for easy monitoring over time
  • 🚀 Integrate with your CDN — After optimization, consider pairing with a CDN that applies on-the-fly format negotiation for older browser fallbacks
💡 Performance Tip: For maximum impact, run your Sharp pipeline in parallel using Promise.all() (as shown in the script above). This can reduce processing time by 5–10x compared to sequential processing on large image sets.

Conclusion

You now have a complete, production-ready automated image optimization workflow — from pre-commit hooks that catch unoptimized files before they're committed, to a CI/CD pipeline that processes images automatically on every push.

The investment is roughly 30 minutes of setup time. The return is permanent — every image that ships to production from this point forward will be compressed, converted to WebP, and properly sized. No exceptions.

For the full context on why this matters and which tools power this workflow, return to: Best Free Image Optimization Tools for Developers.

Need help choosing between Sharp, imagemin, or TinyPNG for your pipeline? Read: Squoosh vs TinyPNG vs Sharp — Complete Developer Comparison.

⚡ Need a Quick Win Right Now?

Before your pipeline is set up, compress your most important images instantly with ConvertIImage — free, no install, no account.

Compress Images Now — Free →

Tools Comparison  |  ← Image Problems Guide  |  ← Full Developer Guide

FAQs

❓ What Node.js version does this pipeline require?

Node.js v18 or higher is recommended. Sharp v0.33+ (current as of 2026) requires Node.js v18.17.0+. Use node -v to check your version and nodejs.org to upgrade if needed.

❓ Can I use this workflow with Vite or Webpack?

Yes. For Vite, use the vite-imagetools plugin. For Webpack, use image-minimizer-webpack-plugin with Sharp as the implementation. These integrate directly into your build config and process images at build time without a separate script.

❓ Should I commit both original and optimized images to Git?

Best practice is to commit only original images to your source directory (e.g., src/assets/images/original/) and add the output directory (e.g., public/images/) to .gitignore. Let your CI pipeline regenerate optimized images on each build. This keeps your repository lean and avoids storing binary duplicates in Git history.

❓ How do I handle images uploaded by users (dynamic content)?

For user-uploaded images, run Sharp on the server side at the point of upload — not in the build pipeline. Create an upload handler that pipes the incoming file through Sharp for compression and WebP conversion before saving it to storage. Alternatively, use a CDN with on-the-fly transformation like Cloudinary or imgix.