How the next-export-optimize-images CLI Performs Image Optimization

The next-export-optimize-images CLI orchestrates a multi-stage pipeline that loads Next.js configuration, builds a comprehensive manifest of local and remote images, and processes them in parallel using Sharp with intelligent caching to eliminate redundant work.

The next-export-optimize-images library extends Next.js static exports with advanced image optimization capabilities. When you run the CLI command, the entry point at bin/index.js bootstraps a sophisticated pipeline that transforms and compresses your assets. Understanding how this CLI performs image optimization helps developers debug build issues and optimize CI/CD performance.

CLI Entry Point and the run() Function

The journey begins in bin/index.js, a thin wrapper that loads the compiled CLI module:

#!/usr/bin/env node
require('../dist/cli').run({})

This invocation triggers the run() function defined in src/cli/index.ts (lines 90‑100). This function serves as the orchestration layer, performing four critical initialization steps:

  1. Load user configuration via getConfig() from src/utils/getConfig.ts
  2. Resolve the manifest path to .next/next-export-optimize-images-list.nd.json
  3. Load Next.js image configuration using loadConfig(PHASE_PRODUCTION_BUILD, cwd) to obtain deviceSizes and imageSizes
  4. Execute optimization by awaiting optimizeImages()

Configuration Loading and Manifest Generation

The optimizeImages function (source: src/cli/index.ts, lines 70‑140) constructs a comprehensive inventory of every image requiring processing. This manifest generation handles three distinct asset sources:

  • Existing export manifest: Parses and de-duplicates entries from previous builds using processManifest() and uniqueItems()
  • Remote images: When config.remoteImages is defined, each URL expands into size-specific entries via buildOutputInfo from src/utils/buildOutputInfo.ts
  • Public folder assets: Recursively scans the public/ directory for PNG, JPG, WEBP, AVIF, and GIF files using recursive-readdir

Each manifest entry conforms to a strict TypeScript interface containing the output filename, source path, target width, desired extension, and optional external URL.

Parallel Processing and Intelligent Caching

To maximize throughput, the CLI groups manifest items by source path into a srcMap. This ensures each source file undergoes exactly one fs.readFile operation regardless of how many sizes or formats derive from it.

The caching system, implemented in src/cli/utils/cache.ts, eliminates redundant processing:

  • Cache directory: Creates defaultCacheDir to store previously optimized files
  • SHA-256 hashing: Generates a hash of the original image buffer to serve as a cache key
  • Cache lookup: Before invoking Sharp, the code checks for existing entries; cache hits trigger a direct file copy and increment cacheMeasurement
  • Cache persistence: Writes updated manifest data via writeCacheManifest

Use the --noCache flag to bypass this behavior and force full re-optimization.

Sharp Image Transformations in getOptimizeResult

The core optimization logic resides in getOptimizeResult (source: src/cli/index.ts, lines 44‑78). This function validates formats, manages file I/O, and executes the Sharp pipeline:

const image = sharp(imageBuffer, { sequentialRead: true, animated: true })
image.rotate().resize({ width, withoutEnlargement: true })

Supported Format Outputs

For each target extension validated by formatValidate (supporting jpeg, jpg, png, webp, and avif), the CLI applies format-specific Sharp methods:

  • JPEG/JPG: image.jpeg({ quality, ...sharpOptions?.jpg })
  • PNG: image.png({ quality, ...sharpOptions?.png })
  • WebP: image.webp({ quality, ...sharpOptions?.webp })
  • AVIF: image.avif({ quality, ...sharpOptions?.avif })

Each processed image writes to two locations simultaneously: the final destination path and the cache directory. Non-optimizable formats like SVG or GIF bypass transformation and copy directly to the output folder, with optional tracking in invalidFormatAssets.

Progress Reporting and Build Summary

The CLI provides real-time feedback through src/cli/utils/cliProgressBar.ts. The cliProgressBarStart function initializes a progress bar based on the manifest length, while cliProgressBarIncrement updates after every processed image—whether cached, optimized, or failed.

Upon completion, the CLI outputs a summary including cache hit ratios, error counts, and lists of non-optimized assets, concluding with a "Successful optimization!" message.

Summary

  • The bin/index.js entry point delegates to run() in src/cli/index.ts, which coordinates the entire pipeline
  • Manifest generation combines Next.js export data, remote images, and public folder scans into a unified processing queue
  • Intelligent caching uses SHA-256 hashing to skip redundant Sharp operations, significantly accelerating rebuilds
  • Sharp transformations handle JPEG, PNG, WebP, and AVIF outputs with configurable quality settings and respect for withoutEnlargement constraints
  • The CLI reports progress via a terminal progress bar and provides detailed metrics on cache efficiency and errors

Frequently Asked Questions

What image formats does the next-export-optimize-images CLI support?

The CLI supports optimization of JPEG, PNG, WebP, and AVIF formats through the Sharp library. The formatValidate function in the source code explicitly checks for these extensions. GIF and SVG files are not processed by Sharp; instead, the CLI copies them directly to the output directory while optionally logging them as non-optimized assets.

How does the caching mechanism work to speed up builds?

The CLI implements a file-based cache system in src/cli/utils/cache.ts. Before processing any image, it calculates a SHA-256 hash of the original file buffer. If a matching hash exists in the cache directory (defaultCacheDir), the CLI copies the cached file instead of running Sharp. This prevents redundant CPU-intensive encoding operations during incremental builds or when images remain unchanged between exports.

Can the CLI optimize remote images fetched from external URLs?

Yes. When the configuration specifies remoteImages, the CLI expands each URL into multiple size-specific entries using buildOutputInfo. These entries include an externalUrl property in the manifest. During processing, the CLI downloads and optimizes these remote assets alongside local files, generatingstatically optimized versions in the export output.

What happens if the CLI encounters an unsupported image format?

If formatValidate determines an extension is not optimizable (such as SVG or GIF), the CLI bypasses the Sharp pipeline entirely. It uses fs.copy to transfer the file unchanged to the destination directory. The system tracks these files in invalidFormatAssets and can report them in the final summary, ensuring developers are aware of which images did not undergo compression.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →