Next.js で output: "export" でも画像最適化をしたい（next-image-export-optimizerを使う）

## この記事の概要

Next.js では`output: "export"`を設定すると、通常の画像最適化は有効になりません。
`next/image`を使用した際、次のようなエラーが出ます。

```
Error: Image Optimization using the default loader is not compatible with `{ output: 'export' }`.
  Possible solutions:
    - Remove `{ output: 'export' }` and run "next start" to run server mode including the Image Optimization API.
    - Configure `{ images: { unoptimized: true } }` in `next.config.js` to disable the Image Optimization API.
  Read more: https://nextjs.org/docs/messages/export-image-api
```

画像の最適化を諦めたくはないので、ライブラリを使って解決する方法を記事にしました。

:::note warn
基本的にはライブラリのドキュメント通りの記載をしていますが、一部私の解釈も入っています。
手元で試した限り動作的には問題ありませんでしたが、もし間違いなどがあればコメントをいただけると嬉しいです。
:::

:::note
よく似たライブラリである[next-export-optimize-imagesを試した記事](https://qiita.com/xrxoxcxox/items/a457e9ceff30c6d53157)も書きました。
ちなみに両方を比較した結果、next-export-optimize-images を選ぶかなあ、という印象です。
:::

## 環境

主要なものだけ記載します。

| 依存関係 | バージョン |
| --- | --- |
| next | 14.2.3 |
| next-image-export-optimizer | 1.13.0 |
| react | 18.3.1 |
| typescript | 5.4.5 |

この記事で話題にするのは [next-image-export-optimizer](https://github.com/Niels-IO/next-image-export-optimizer) です。

## インストールとセットアップ

コマンドを叩きます。

```sh
npm install next-image-export-optimizer
```

---

次に`next.config.mjs`を編集します。
最低限の記載（すべて[デフォルト値](https://github.com/Niels-IO/next-image-export-optimizer/blob/db79f0cb55105eed362aed825f1815f1a86a2c8a/src/optimizeImages.ts#L71-L81)を使用する）なら次のようになります。

```diff_javascript:next.config.mjs
  /** @type {import('next').NextConfig} */
  const nextConfig = {
    output: "export",
+   images: { loader: "custom" },
+   transpilePackages: ["next-image-export-optimizer"],
  };

  export default nextConfig;
```

省略せず、すべてを記載すると次のようになります。
（ただしここで記載しているすべての値はデフォルト値のままなので、得られる結果は上に記載しているものと何も変わりません）

```diff_javascript:next.config.mjs
  const nextConfig = {
    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",
+   },
  }
```

デフォルト値から変えることが多そうなのは`imageSizes`と`deviceSizes`でしょうか。
ここに記載してある種類が多いほど書き出される画像数が増え、ビルド時間が伸びます。

画像にもよると思いますが、試しに作ってみた環境では 3 枚の画像を最適化するだけで 10 秒ほどかかってしまいました。

---


また`package.json`のスクリプトも書き換えます。

```diff_json:package.json
  "scripts": {
-   "build": "next build",
+   "build": "next build && next-image-export-optimizer",
  }
```

これにより、ビルド時に`next.config.js`での指定にあわせて複数の画像が書き出されます。

---

最後に`next/image`を`next-image-export-optimizer`に置き換えます。

```diff_javascript
- import Image from "next/image";
+ import ExportedImage from "next-image-export-optimizer";
  import imgSrc from "path/to/image";
  
- <Image src={imgSrc} alt="Image description" />;
+ <ExportedImage src={imgSrc} alt="Image description" />;
```

## ドキュメントに書いていないけど、実施しておいた方が良さそうなこと

### 開発時でも一度画像を書き出す必要がある

scripts の`next-image-export-optimizer`を走らせないまま`next dev`をすると、次のようになります。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214677/50fe107a-738f-4b8f-dd3e-0346768040c2.png)

`<ExportedImage />`は`public/images/nextImageExportOptimizer`の中に画像ファイルがあることを期待しているのですが、見つからないためエラーになっている、という状態です。

開発環境での表示自体はできるのですが、画像の数だけエラーが増えるのでかなり見づらくなります。
解消するためには、次のどちらかを実施します。

1. `npm run dev`の前に`npm run build`をする
1. `dev`コマンドに`next-image-export-optimizer`をセットにする

1 番目の内容は読んで字の如くなので省略します。
2 番目の内容は、scripts を次のように書き換えます。

```diff_json:package.json
  "scripts": {
-   "dev": "next dev",
+   "dev": "next-image-export-optimizer && next dev",
  }
```

なお、このやり方が正しいのかは怪しいです。

公式ドキュメントにもありませんし、私が試した限り挙動に問題が無かっただけで、環境などが違えば何かが起きるかもしれません。

ただ「開発する前に一度ビルドする」といった、人間の記憶に頼ったやり方よりは良いかな？と思っている次第です。

### `.gitignore`に画像の書き出し先を指定しておいた方が良さそう

ここまでの設定のままだと`public/images/nextImageExportOptimizer`にある画像がすべて git 管理下に置かれます。

完全にダメということでもないと思いますが、画像を追加・削除するだけで Pull Request の diff がかなり増えてしまうなど、面倒なことも多そうです。

実際、example として用意されているコードでは `.gitignore`に`public/images/nextImageExportOptimizer`が記載されています。

https://github.com/Niels-IO/next-image-export-optimizer/blob/db79f0cb55105eed362aed825f1815f1a86a2c8a/example/.gitignore