Library / SDK
Niels-IO/next-image-export-optimizer avatar
Niels-IO/next-image-export-optimizer

next-image-export-optimizer: responsive images for a Next.js static export

Use Next.js advanced <Image/> component with the static export functionality. Optimizes all static images in an additional step after the Next.js static export.

541 stars66 forksJavaScriptMIT

At a glance

What is it?
next-image-export-optimizer gives a statically exported Next.js site the responsive image behaviour of the built-in Image component, by running sharp over your images in a step after the export. The trade-offs are visible in the config: one global quality value, remote images listed by hand, and a build that has to be run twice.
Who is it for?
Adopt next-image-export-optimizer if you are deploying a Next.js site as a static export and the loss of the image optimiser is a real problem, and you can accept a post-export build step and a hand-maintained list of remote image URLs. Do not adopt it if your deployment runs a Next.js server, since the built-in optimiser already handles that case, or if you need per-image quality control.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 8 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 25, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the static export leaves behind

Next.js ships an advanced Image component that resizes and re-encodes images on the server, generating the sizes a browser actually needs. That component depends on a running Next.js server, so a site deployed with the static export functionality loses it. The optimisations come back only as static files, and the original sources are shipped as they are.

next-image-export-optimizer exists to close that gap. The README describes it as using the advanced Image component with the static export functionality, optimising all static images in an additional step after the Next.js static export.

The step is the key architectural point. The package is not a build plugin that hooks into the Next.js compiler, it is a separate command that runs after next build and walks the exported output. The result is a static site whose images behave like a server-optimised one, served from a CDN with no server required.

The audience is a developer who wants a static deployment, on a CDN or in object storage, and who does not want to hand-optimise every image or accept the full-resolution originals in the bundle. The feature list is explicit about what you get: reduced image size and page load times through responsive images, fast transformation using sharp.js, the same library the Next.js server uses in production, conversion of JPEG and PNG to WEBP by default, automatic generation of tiny blurry placeholder images, TypeScript support, remote images downloaded and optimised, and animated images in GIF and WEBP format.

Installing the package and wiring the build script

The install is a normal dependency, and the README lists three package managers:

bash
npm install next-image-export-optimizer

# Or
yarn add next-image-export-optimizer
pnpm install next-image-export-optimizer

The package metadata adds two constraints worth knowing. The engines field requires Node 20.9.0 or newer, and the binary entry point is dist/optimizeImages.js, which is the command you will run in the build script.

Wiring it into the build is a one-line change to the build command in package.json, and the README shows it as a diff:

diff
{
-  "build": "next build",
+  "build": "next build && next-image-export-optimizer"
}

The order matters and is the whole mechanism. next build runs first and produces the export, then the optimiser runs against that output. Nothing happens during the Next.js compilation itself, which is why this approach works with the static export at all.

The package also exposes a legacy export path at next-image-export-optimizer/legacy/ExportedImage, which is how a project that used the older component entry point can keep importing it while the main path has moved on. Both the modern and the legacy files are published in the files list of the package manifest, along with the dist/utils directory.

The next.config.js environment block that drives everything

All configuration lives in the env block of next.config.js, and the README says you can also use next.config.ts for TypeScript projects. The basic configuration is a single object:

javascript
// next.config.js
module.exports = {
  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",
  },
};

Several of these are load bearing. The images block sets the loader to custom and defines the imageSizes and deviceSizes arrays, which are the widths the optimiser will generate. The transpilePackages entry is needed so the package is compiled by the Next.js build rather than treated as an external dependency.

The env keys are all prefixed nextImageExportOptimizer_ and each maps to a specific behaviour: imageFolderPath points at the folder in public where your images live, exportFolderPath is the export output directory, quality sets the compression level, storePicturesInWEBP controls the format conversion, exportFolderName is the subfolder the optimised files are written to, generateAndUseBlurImages turns on the placeholder generation, and remoteImageCacheTTL controls how long a downloaded remote image is reused.

The quality value of 75 is the default, and the README calls out a constraint that matters in review: only one global value can be used for the image quality setting. There is no per-image override, so a site with photographic hero images and flat illustrations will be stuck with one compromise.

Swapping Image for ExportedImage

The component change is the visible part of the integration. The README shows the original usage with the standard Image component and then the replacement with ExportedImage.

For a path string, the image has to sit in a folder inside the public folder, for example public/images, and you pass the path as you did before. For a static import, the image can live anywhere in your project, and the optimiser will process it and copy it to the export folder.

The import comes from the package itself, and the README recommends the static import form:

javascript
// With static import (Recommended)
import ExportedImage from "next-image-export-optimizer";
import testPictureStatic from "PATH_TO_IMAGE/test_static.jpg";

<ExportedImage src={testPictureStatic} alt="Static Image" />;

The dynamic form keeps the same props as the original component, including width and height:

javascript
// With dynamic import
import ExportedImage from "next-image-export-optimizer";

<ExportedImage
  src="images/VERY_LARGE_IMAGE.jpg"
  alt="Large Image"
  width={500}
  height={500}
/>;

The repository layout backs up the testing story. There is a jest.config.js for unit tests and two Playwright setups, playwright.config.js and playwright-basePath.config.js, with a matching npm script for each in the package manifest. The basePath variant runs with BASEPATH=true and IMAGESWEBP=false, so the end-to-end suite exercises a subfolder deployment and the non-WEBP path as well as the default one. The example/ directory is a full Next.js application with its own next.config.ts and its own remoteOptimizedImages.js, and the package scripts include an exportExample target that builds and exports it.

Remote images have to be declared by hand

Remote images are the part of this package with the most friction, and the reason is structural. Because optimisation happens at build time rather than at request time, the optimiser has to know in advance which remote URLs exist. The README states that you have to specify all remote image urls in a file called remoteOptimizedImages.js in the root directory of your project, where next.config.js is stored as well.

The file exports an array of strings containing the URLs, and returning a promise of such an array is also supported:

javascript
// remoteOptimizedImages.js
module.exports = [
  "https://example.com/image1.jpg",
  "https://example.com/image2.jpg",
  "https://example.com/image3.jpg",
  // ...
];
javascript
// Or with an async API call
module.exports = fetch("https://example.com/api/images").catch((error) => {
  console.error(error);
  return []; // return an empty array in case of error
});

That last form is the one to look at closely. If the fetch fails, the catch returns an empty array, and the build continues with no remote images optimised, which means the affected images are simply not optimised rather than the build failing loudly. A source of silent partial output.

At build time the images are either downloaded or read from the cache, and the URLs in the ExportedImage component are replaced with the optimised image URLs. The cache lifetime is set in seconds through nextImageExportOptimizer_remoteImageCacheTTL, and the default of 0 seconds exists because the image might have changed. The README lists the values to use for a minute, an hour, a day, a week, a month and a year.

If you need to hide the remote URLs from the user, the overrideSrc prop replaces the src attribute of the image tag. The warning attached to it is real: the Image component cannot fall back to the original image URL if the optimised images are not yet generated, so with overrideSrc you get a broken image link rather than a slow one.

Monorepos, custom paths and the deployment base path

Three configuration escape hatches cover the layouts a static export usually lands in.

The first is a custom next.config.js path, for projects where the Next.js application is not in the directory where you run the commands, a monorepo being the case the README names. You pass the location as an argument to the script:

json
"export": "next build && next-image-export-optimizer --nextConfigPath path/to/my/next.config.js"

The second is a custom export folder path, which can be set with the nextImageExportOptimizer_exportFolderPath environment variable in the config, or passed as the --exportFolderPath argument to the script:

json
 "export": "next build && next-image-export-optimizer --exportFolderPath path/to/my/export/folder"

The third is the basePath, set in next.config.js as a normal Next.js option, for a site deployed into a subfolder of a domain:

javascript
module.exports = {
  basePath: "/subfolder",
};

That last one is why the package ships a second Playwright configuration. A subfolder deployment changes the URLs of every generated asset, and a library that rewrites image paths has to be tested against exactly that case rather than assumed to work.

The repository layout supports this story. There is a tsconfig.optimizeImages.json alongside the main tsconfig.json, so the CLI and the React component are compiled as two separate TypeScript projects with different outputs, and the build script runs rimraf, then tsc, then the second tsc invocation. The package is MIT licensed, the author is Niels Grafen, and the release record shows v1.21.1 on 2026-09-02 with a version of 1.21.2 in the package manifest, so the repository is slightly ahead of the last tagged release.

Editorial conclusion

Adopt next-image-export-optimizer if you are deploying a Next.js site as a static export and the loss of the image optimiser is a real problem, and you can accept a post-export build step and a hand-maintained list of remote image URLs. Do not adopt it if your deployment runs a Next.js server, since the built-in optimiser already handles that case, or if you need per-image quality control. Verify first that the images in your public folder are the ones the optimiser expects, that every remote image URL appears in remoteOptimizedImages.js, and that your Node version is at least 20.9.0 as the package requires.

Frequently asked questions

How do I install next-image-export-optimizer?

Add it as a dependency with npm install next-image-export-optimizer, yarn add next-image-export-optimizer or pnpm install next-image-export-optimizer. The package requires Node 20.9.0 or newer.

How do I use next-image-export-optimizer with a static export?

Set output to export in next.config.js, change the build script to next build && next-image-export-optimizer, and replace the Image component with ExportedImage imported from next-image-export-optimizer. The optimiser runs as a separate step after the Next.js build.

How are remote images handled by next-image-export-optimizer?

All remote image URLs have to be listed in a file called remoteOptimizedImages.js in the project root, as an array of strings or a promise of one. At build time the images are downloaded or read from the cache, and the URLs are replaced with the optimised image URLs.

Can I set a different image quality per image in next-image-export-optimizer?

No. The documentation states that only one global value can be used for the image quality setting, and the default value is 75. The setting is nextImageExportOptimizer_quality in the next.config.js env block.

Does next-image-export-optimizer support a base path deployment?

Yes, the basePath is set in next.config.js as a normal Next.js option, for example /subfolder. The package also ships a separate Playwright configuration, playwright-basePath.config.js, whose test script runs with BASEPATH=true to exercise that case.

Official sources

  1. Issues
  2. License: MIT
  3. Niels-IO/next-image-export-optimizer on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/niels-io-next-image-export-optimizer.svg)](https://hysenlabs.com/projects/niels-io-next-image-export-optimizer)

Was this useful?