# How to Download External Images Locally for Optimization with Next.js

> Optimize external images in Next.js by downloading them locally during static export. Learn how next-export-optimize-images automates this process for better performance.

- Repository: [d-suke/next-export-optimize-images](https://github.com/dc7290/next-export-optimize-images)
- Tags: how-to-guide
- Published: 2026-02-28

---

**`next-export-optimize-images` automatically downloads remote images, stores them in your build output, and processes them through Sharp-based optimization during static export.**

The `dc7290/next-export-optimize-images` library extends Next.js's standard image workflow to handle external URLs. Instead of manually downloading remote assets before building, you declare the URLs in your configuration, and the CLI fetches, caches, and optimizes them alongside your local images.

## The Three-Step External Image Pipeline

The library handles remote images through a distinct pipeline implemented across several core modules. Understanding this flow helps debug issues and optimize build performance.

**1. Manifest Generation with External URLs**

In [`src/cli/index.ts`](https://github.com/dc7290/next-export-optimize-images/blob/main/src/cli/index.ts), the CLI constructs a manifest that bridges the gap between remote sources and local optimization. When you provide the `remoteImages` option, the code calls `buildOutputInfo` (from [`src/utils/buildOutputInfo.ts`](https://github.com/dc7290/next-export-optimize-images/blob/main/src/utils/buildOutputInfo.ts)) for each URL to generate size variants. Each entry in the resulting manifest includes an `externalUrl` field containing the original remote address, plus the target `output` path where the file should live locally (lines 84‑104, 185‑210).

**2. Network Fetch via the Downloader**

Before any image processing begins, the `externalImagesDownloader` function (located in [`src/cli/external-images/index.ts`](https://github.com/dc7290/next-export-optimize-images/blob/main/src/cli/external-images/index.ts)) reads the manifest. It filters for entries containing `externalUrl`, fetches each image over HTTP, and writes the bytes to disk under the directory specified by `externalImageDir` (default `'_next/static/media'`). This module respects the throttling parameters `remoteImagesDownloadsDelay` and `processingConcurrency` to prevent overwhelming external servers or hitting rate limits.

**3. Standard Optimization Pass**

Once downloaded, the images exist as local files. The CLI then proceeds to `getOptimizeResult`, passing the same manifest entries—now with local `src` paths—to the Sharp-based optimization pipeline. From this point onward, remote and local images follow identical processing logic, including format conversion, quality compression, and responsive variant generation.

## Configuring Remote Images in [`export-images.config.js`](https://github.com/dc7290/next-export-optimize-images/blob/main/export-images.config.js)

The configuration loader in [`src/utils/getConfig.ts`](https://github.com/dc7290/next-export-optimize-images/blob/main/src/utils/getConfig.ts) supports both static arrays and dynamic async functions for the `remoteImages` field. This flexibility allows you to fetch image lists from CMS APIs at build time.

**Basic Static Configuration**

```javascript
/** @type {import('next-export-optimize-images').Config} */
const config = {
  remoteImages: [
    'https://picsum.photos/id/237/800/600.jpg',
    'https://picsum.photos/id/238/800/600.jpg',
  ],
  quality: 80,
}
module.exports = config

```

**Dynamic Async Configuration**

```javascript
const config = {
  remoteImages: async () => {
    const response = await fetch('https://api.example.com/images');
    const data = await response.json();
    return data.map(img => img.url);
  },
}
module.exports = config

```

The library evaluates stringified functions for advanced use cases like `filenameGenerator` and `sourceImageParser`, but `remoteImages` can be provided as a standard array or async function returning an array of URL strings.

## CLI Execution and Manifest Structure

When you run `npx next-export-optimize-images`, the entry point at [`src/cli/index.ts`](https://github.com/dc7290/next-export-optimize-images/blob/main/src/cli/index.ts) orchestrates the workflow. It first generates an ND‑JSON manifest file where each line represents an image variant. Entries originating from external sources carry the `externalUrl` property.

**Example Manifest Entry**

```json
{
  "output": "/_next/static/media/7c4a8c1e2d3f4a5b6c7d8e9f0a1b2c3d.jpg",
  "src": "/_next/static/media/7c4a8c1e2d3f4a5b6c7d8e9f0a1b2c3d.jpg",
  "width": 800,
  "extension": "jpeg",
  "externalUrl": "https://picsum.photos/id/237/800/600.jpg"
}

```

The `src` field points to the local cache location (prefixed according to your build mode), while `externalUrl` signals to the downloader that a network fetch is required. After `externalImagesDownloader` completes, the manifest is processed by `processManifest` ([`src/utils/processManifest.ts`](https://github.com/dc7290/next-export-optimize-images/blob/main/src/utils/processManifest.ts)) and fed into the optimization worker pool.

## Throttling and Concurrency Controls

Downloading numerous large images simultaneously can trigger rate limiting or memory pressure. The library provides two configuration options to manage this, both passed directly to the downloader module:

- **`remoteImagesDownloadsDelay`**: Milliseconds to wait between individual downloads (default: 0)
- **`processingConcurrency`**: Maximum number of concurrent downloads (default: system-dependent)

```javascript
const config = {
  remoteImages: ['https://example.com/gallery/1.jpg', 'https://example.com/gallery/2.jpg'],
  remoteImagesDownloadsDelay: 200,  // Wait 200ms between each request
  processingConcurrency: 5,         // Download max 5 images simultaneously
}

```

These settings are particularly important when sourcing hundreds of images from a single domain or when running in resource-constrained CI environments.

## Customizing the Download Directory

By default, external images are stored in `_next/static/media` within your output directory. You can redirect this to the `public` folder if you need the raw files included in your static export for non-Next.js consumption:

```javascript
const config = {
  externalImageDir: 'public/remote',
  remoteImages: ['https://example.com/logo.png'],
}

```

This writes files to `public/remote/<hash>.png`, making them available at `/remote/<hash>.png` after export while still allowing the optimizer to generate compressed variants in the standard `_next` directory.

## Summary

- **Declare external URLs** in [`export-images.config.js`](https://github.com/dc7290/next-export-optimize-images/blob/main/export-images.config.js) using the `remoteImages` option, which accepts arrays or async functions.
- **Manifest construction** in [`src/cli/index.ts`](https://github.com/dc7290/next-export-optimize-images/blob/main/src/cli/index.ts) tags remote entries with `externalUrl` and maps them to local cache paths.
- **Download phase** handled by [`src/cli/external-images/index.ts`](https://github.com/dc7290/next-export-optimize-images/blob/main/src/cli/external-images/index.ts), which respects `remoteImagesDownloadsDelay` and `processingConcurrency` settings.
- **Optimization phase** treats downloaded files identically to local assets, passing them through the Sharp pipeline via `getOptimizeResult`.
- **Storage location** defaults to `_next/static/media` but is configurable via `externalImageDir`.

## Frequently Asked Questions

### How does `next-export-optimize-images` handle external image failures?

If an external image fails to download (network error, 404, etc.), the `externalImagesDownloader` throws an error that halts the build process. Unlike local images, remote failures are considered blocking because the optimizer cannot generate the requested output variants without the source bytes. You should validate URLs before build time or wrap the CLI in error-handling logic if you need graceful degradation.

### Can I use wildcards or glob patterns in `remoteImages`?

No, the `remoteImages` option requires explicit URLs or an async function that returns an array of strings. The library does not perform crawling or glob resolution. If you need to optimize dynamic sets of images, implement the fetching logic inside the async `remoteImages` function to query your CMS or asset API and return the complete list of URLs at build time.

### What image formats are supported for external downloads?

The downloader fetches the raw bytes regardless of format, and the optimization pipeline (via Sharp) handles JPEG, PNG, WebP, AVIF, and GIF. The format of the downloaded file does not constrain the output; you can configure the library to convert remote JPEGs to WebP or AVIF during optimization using the standard format options in [`export-images.config.js`](https://github.com/dc7290/next-export-optimize-images/blob/main/export-images.config.js).

### Is there a cache for downloaded external images between builds?

Yes, downloaded external images are written to the directory specified by `externalImageDir` (default `'_next/static/media'`). If this directory persists between builds (for example, if you cache the `.next` folder in CI), the CLI will skip re-downloading images that already exist locally unless the manifest entries change. This behavior mirrors how local images in `public/` are treated during incremental builds.