# Next.js static export images: the default loader breaks them and unoptimized ships the original

**Author:** SJ | **Published:** 2026-11-04 | **Tags:** WebP, JPEG | **URL:** https://www.iminify.com/blog/32-nextjs-static-export-images-the-default-loader-breaks-them-and-unoptimized-ships-the-original

---

We built a page with five photographs in a Next.js 16.4.3 App Router app, set `output: 'export'`, left the image settings alone, and `next build` finished with exit code 0. In Chrome none of the five images loaded: every `src` pointed at `/_next/image`, an endpoint a static export doesn't contain. Setting `images: { unoptimized: true }` fixes that, and the export then serves the file you put in `public/`. Chrome at phone size downloaded 2.4 MB for five 1800 px photographs, and 55.2 MB when the same page held five 4K Gemini JPEGs.

This post compares that flag, next-image-export-optimizer and resizing the files first by what Chrome downloaded at phone size and at desktop size. The table is in the second section, and the choice we'd make follows the three options.

<!--more-->

## The App Router build passes and the images 404

Next's [error page](https://nextjs.org/docs/messages/export-image-api) says the default loader "relies on the Image Optimization API which is not available for exported applications", and its [static export guide](https://nextjs.org/docs/app/guides/static-exports) lists "Image Optimization with the default `loader`" as unsupported. So we ran both commands in 16.4.3. `next dev` answered HTTP 500 with the error text, and a Pages Router build with one image exited 1 with a similar message. Our App Router `next build` exited 0 after 3 seconds and printed no warning. `out/index.html` mentioned `/_next/image` 53 times and `out/` had no folder for it, so our static server answered 404, and in Chrome all five images had a `naturalWidth` of 0.

To catch it before you deploy, search the HTML in `out/`:

```bash
grep -rl --include='*.html' '/_next/image' out
```

It printed `out/index.html` for the default loader and nothing for the unoptimized, optimizer and full-size Smart exports. Without `--include`, all four also matched a JavaScript chunk. It exits 0 when it finds a match, so a CI step wants a `!` in front.

`basePath` is a second way to get 404s. With `basePath: '/site'` and `unoptimized`, the exported tag kept `src="/images/landscape.jpg"`. Served under `/site/`, that address returned 404 and `/site/images/landscape.jpg` returned 200. Next's [basePath docs](https://nextjs.org/docs/app/api-reference/config/next-config-js/basePath) say you add the prefix to `src` yourself.

## unoptimized ships the file as it is and drops srcset

The setting the error page lists goes in `next.config.mjs`:

```js
// next.config.mjs
export default {
  output: 'export',
  images: { unoptimized: true },
}
```

The [image docs](https://nextjs.org/docs/app/api-reference/components/image) call the result "served as-is". Our `out/images/` held the sources byte for byte (SHA-256 matched on all ten files), and the exported HTML had no `srcset` and no `sizes` attribute, although the page sets `sizes`. Every screen gets the one file.

The table is what Chrome downloaded for the same page in four setups, once with the five 1800 px photographs of [our first post](https://www.iminify.com/blog/1-ssimulacra-2-scores-in-practice-what-50-70-and-90-cost-in-kilobytes) and once with five 4K Gemini JPEGs of 5504 by 3072 pixels. Smart is Iminify's default compression level, a higher score means closer to the original, and the last row is the optimizer described below:

| Setup | Photographs, phone / desktop | 4K JPEGs, phone / desktop | Score, photographs / 4K |
|---|---|---|---|
| `unoptimized`, files as they are | 2.40 MB / 2.40 MB | 55.2 MB / 55.2 MB | 100 / 100 |
| `unoptimized`, Smart JPG at full size | 1.09 MB / 1.09 MB | 10.2 MB / 10.2 MB | 77.1 / 83.0 |
| `unoptimized`, Smart JPG 1200 px wide | 580 KB / 580 KB | 1.29 MB / 1.29 MB | 80.8 / 79.1 |
| next-image-export-optimizer, WebP 75 | 315 KB / 180 KB | 767 KB / 221 KB | 68.0 / 73.2 |

Phone means 390 px wide at 3x and desktop 1440 px at 1x, both scrolled to the end. Scores are the median SSIMULACRA 2 of the five delivered files against the master at the delivered size, so 100 is the master itself. For the optimizer, the scores are of the 1200 px files the phone received.

The first row is 72 times what the optimizer sent the phone for the 4K files, and about 250 times what it sent the desktop.

## next-image-export-optimizer gives a phone one file and a desktop another

[next-image-export-optimizer](https://github.com/Niels-IO/next-image-export-optimizer) had [58,022 npm downloads](https://api.npmjs.org/downloads/point/2026-10-23:2026-10-29/next-image-export-optimizer) in the week to 29 October 2026, and we ran version 1.21.2. It runs after `next build` and writes each image at every width in your `imageSizes` and `deviceSizes`. This is its README configuration, written as a `next.config.mjs`:

```js
// next.config.mjs
export default {
  output: 'export',
  images: {
    loader: 'custom',
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
    deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
  },
  transpilePackages: ['next-image-export-optimizer'],
  env: {
    nextImageExportOptimizer_imageFolderPath: 'public/images',
    nextImageExportOptimizer_exportFolderPath: 'out',
    nextImageExportOptimizer_quality: '75',
    nextImageExportOptimizer_storePicturesInWEBP: 'true',
    nextImageExportOptimizer_exportFolderName: 'nextImageExportOptimizer',
    nextImageExportOptimizer_generateAndUseBlurImages: 'true',
    nextImageExportOptimizer_remoteImageCacheTTL: '0',
  },
}
```

Install it with `npm install next-image-export-optimizer`, make the build script `next build && next-image-export-optimizer`, and import `ExportedImage` from the package where the page used `Image`. For each image it writes a `-opt-<width>.WEBP` file at 17 widths: the 8 in `imageSizes`, the 8 in `deviceSizes` and a 10 px placeholder, 85 files for our five images. The `<img>` gets a `srcset`: the phone took the 1200 px file for all five images, and the desktop took 1200 px for the hero and 384 px for each card.

In our test its WebP at quality 75 was the one `next/image` makes: the 1080 px and full-size files for the five photographs were SHA-256 identical to what our self-hosted Next.js 16.4.3 returned for `q=75` on 1 November, 10 of 10 ([post 26](https://www.iminify.com/blog/26-what-image-quality-does-nextjs-ship-webp-75-avif-47-if-you-list-it-and-by-default-a-90-becomes-75) covers that pipeline). The WebP scored 68.0 at the median on the 1200 px photographs, below the 70 that [the scale](https://www.iminify.com/blog/1-ssimulacra-2-scores-in-practice-what-50-70-and-90-cost-in-kilobytes#the-ssimulacra-2-scale-is-a-set-of-viewing-tests) calls high quality.

It costs files and disk. `out/` kept the originals beside the 85 new files, so the export of the photographs grew from 3.04 MB to 6.05 MB and the 4K export from 55.8 MB to 65.7 MB. From a clean `public/images`, the optimizer took 3 to 4 seconds on each of three builds for the photographs and 15 to 20 seconds for the 4K files.

Quality is one global number. At quality 85 the phone downloaded 495 KB of photographs at a score of 77.3, and 1.08 MB of 4K files at 80.4. Smart at 1200 px sent 580 KB at 80.8 and 1.29 MB at 79.1, so on the photographs the optimizer was smaller and scored lower, and on the 4K files it was smaller and scored higher.

## Resizing first gives every screen the same file

The other road keeps `unoptimized: true` and sends files that are already the displayed size. Any tool that resizes and compresses does this step. We used Iminify, our compressor, through its [API](https://www.iminify.com/docs/images): `level` smart, `format` jpg, `width` 1200. The [compress JPEG page](https://www.iminify.com/compress-jpeg) has the same Smart level and a width setting under Size, for a batch dropped in the browser.

`unoptimized` leaves the `<img>` without a `srcset`, so one file serves both screens, and a 1200 px hero on a 2x desktop would be drawn at half the display's density. Iminify's Pro plan can also write a responsive set with a `srcset` snippet, which we didn't test.

## What we'd pick

One width for every screen: resize and compress before the build, and keep `unoptimized: true`. It adds nothing to `next build`.

Different widths on different screens, as with the hero and cards of our test page: next-image-export-optimizer, with a quality you choose and a deploy folder that also holds the originals.

Either way, don't deploy 4K originals under `unoptimized`.

## How we measured

One page, built in Docker with Next.js 16.4.3 (Turbopack, App Router), React 19.3.1 and sharp 0.35.5: a hero up to 1200 px wide and four cards, `sizes` set, lazy loading left on. Chrome 156 was driven through DevTools at 390 by 844 at 3x and 1440 by 900 at 1x from a local static server with caching off, scrolled to the bottom. Each `currentSrc` was mapped to its file's bytes in `out/`, so the figures are file bytes, with KB and MB at 1,000 and 1,000,000 bytes; post 1 lists its photographs in 1,024-byte KB. The optimizer's pages were loaded twice and picked the same files.

We ran 15 of the 20 Smart uploads twice, and every pair of outputs was byte for byte identical. The 4K files are 16.9 megapixels, and Smart's SSIMULACRA 2 check stops at 8 megapixels, so Smart held them to its PSNR floor alone at full size. Scores are SSIMULACRA 2 (libjxl 0.12.0) after decoding with `djpeg` and `dwebp`, against the master resized by sharp for the resized files. The optimizer resizes with sharp too, so its scores leave out the difference between Iminify's resize and sharp's, which the Smart 1200 px scores include. The 4K files come from our September Gemini sample set (Nano Banana 2 and Pro): four are among the 52 files [post 15](https://www.iminify.com/blog/15-how-big-is-a-nano-banana-image-52-files-measured-from-512-px-to-4k) measured, and the fifth is a Nano Banana 2 file from the same set, made through generateContent. [Google's image page](https://ai.google.dev/gemini-api/docs/image-generation) now lists Nano Banana 2.1 as the current model, which we did not use.

We did not test a CDN loader, which the Next.js guide uses for its own image example, or AVIF, Firefox, Safari or a 2x desktop.
