next/imageとSSG

Next.jsをWebサイトの制作方面で試しているのですが、SSRにSG(Static Generation)を持ち込んだ合理的な処理は素晴らしいなと思います。

ただ、SSG(Static Site Generator)として使おうとすると一気に厳しくなるあたり、Next.jsの立ち位置がよく現れていると思います。画像の扱いなんて特に…。

Next.jsでの画像の扱い

Next.jsの画像最適化であるnext/imageは、必要なものをオンデマンドで処理する非常に合理的なものです。そのため、内部に画像最適化のためのAPIを用意し、イメージコンポーネントはそこへ向けたURLで構成されたレスポンシブイメージのコードを出力しています。

ただ、こうした構成のため、APIを用意できないSSGとはなじまないことになります。

実際、next/imageを使った状態でSSGとして出力するnext exportを実行すると、こんなエラーメッセージ(https://nextjs.org/docs/messages/export-image-api)が表示されます。

Error: Image Optimization using Next.js' default loader is not compatible with `next export`.
Possible solutions:
- Use `next start` to run a server, which includes the Image Optimization API.
- Use any provider which supports Image Optimization (like Vercel).
- Configure a third-party loader in `next.config.js`.
- Use the `loader` prop for `next/image`.

ただ、

default loader is not compatible with `next export`.

そして、

Use the `loader` prop for `next/image`

とあります。next/imageのドキュメント(https://nextjs.org/docs/api-reference/next/image)の方にも、

The next/image component's default loader is not supported when using next export. However, other loader options will work.

とあります。つまり、APIを用意すれば、SSGでもnext/imageは使えるということのようです。

microCMS(imgix)の画像とnext/image

そこで、imgixが使えるmicroCMSが相性が良さそうなので、ちょっと試してみました。

const microCMSLoader = ({ src, width, quality }) => {
return `${src}?auto=format&fit=max&w=${width}`
}

という感じで、microCMS用のloaderを用意して、

<figure style={{ position: "relative", height: "clamp(150px,26vw,200px)", }}>
<Image
loader={microCMSLoader}
src={content.eyecatch.url}
alt=""
layout="fill"
objectFit="cover"
sizes="(min-width: 960px) 480px, 50vw"
/>
</figure>

こんな感じでloader propsに設定します。すると、microCMSのimgixで最適化を行うコードになります。

<figure style="position: relative; height: clamp(150px, 26vw, 200px);">
<span style="box-sizing: border-box; display: block; overflow: hidden; width: initial; height: initial; background: none; opacity: 1; border: 0px; margin: 0px; padding: 0px; position: absolute; inset: 0px;">
<img alt=""
sizes="(min-width: 960px) 480px, 50vw"
srcset="https://images.microcms-assets.io/…/everyday.jpg?auto=format&fit=max&w=384 384w, https://images.microcms-assets.io/…/everyday.jpg?auto=format&fit=max&w=640 640w, …略…, https://images.microcms-assets.io/…/everyday.jpg?auto=format&fit=max&w=3840 3840w"
src="https://images.microcms-assets.io/…/everyday.jpg?auto=format&fit=max&w=3840"
decoding="async" data-nimg="fill"
style="position: absolute; inset: 0px; box-sizing: border-box; padding: 0px; border: none; margin: auto; display: block; width: 0px; height: 0px; min-width: 100%; max-width: 100%; min-height: 100%; max-height: 100%; object-fit: cover; filter: none; background-size: cover; background-image: none; background-position: 0% 0%;">
<noscript></noscript>
</span>
</figure>

これなら、next exportでも問題ないのでは?
ということで、next exportでSSGにチャレンジすると、やっぱり、このメッセージが…。

Error: Image Optimization using Next.js' default loader is not compatible with `next export`.
Possible solutions:
- Use `next start` to run a server, which includes the Image Optimization API.
- Use any provider which supports Image Optimization (like Vercel).
- Configure a third-party loader in `next.config.js`.
- Use the `loader` prop for `next/image`.

原因がわからず、色々と検索していたらたどり着いたのがこちらのissueです。

Custom loaders are not recognized by next export
https://github.com/vercel/next.js/issues/21079

ようするに、loader propsに加えて、next.config.js に loader: 'custom' を追加しましょう(Next.js11以降)ということです。

そこでこんな感じに追加すると、問題なくnext exportが通るようになります。

const nextConfig = {
images: {
loader: 'custom',
domains: ['images.microcms-assets.io'],
},
}

解決策は4択じゃなかったんですね…(Built-in Loadersの説明ってそういうことだったんだと、ここで理解)。

Next.jsでの画像の最適化

Next.jsでは、「画像の最適化はオンデマンドで行う」というのが基本です。これはSSGの場合も同様で、GatsbyImageのように「あらかじめ画像を用意する」という形はあまりなじまないのでしょう。

next-optimized-images
https://github.com/cyrilwanner/next-optimized-images

などもありますが、決してお手軽とは言えないと思いますし、Static Imports との兼ね合いもあります。

Imgix、Cloudinary、Akamaiといった外部の画像最適化APIを利用するか、microCMSのような画像最適化APIを装備したHeadless CMSを選択するか、自前でAPIを用意するか。外部にAPIを用意した上でSSGでもnext/imageを使うというのが、Next.jsのスタイルのように思えます。Core Web Vitalsを考慮した設定も活かせますので。

もっとも、自分が試した範囲ではNetlifyでnext/image、ISG、ISR(ちょっと不安定?)が動きましたので、Next.jsの良さを消してまで無理にSSGにする必要ももうないのかなと思い始めています。

"next": "12.0.3",
"@netlify/plugin-nextjs": "^4.0.0-beta.11",