🏞️

Next.jsのnext/imageコンポーネントの使い方と注意点

2020/10/28に公開
2

2020/10/27にNext.js 10がリリースされました。変更点は公式のブログがとても分かりやすくまとまっています。Next.js Confの動画をチェックするのも良いかもしれません(演出が良い感じです)。

Next.js 10で特に注目すべきは、画像最適化をしてくれるnext/imageコンポーネント(next/image)のビルトインサポートだと思います。この記事ではnext/imageについてまとめます。

next/imageとは

next/imageは、画像の表示を最適化してくれるコンポーネントです[1]

画像の最適化

具体的には、以下のような最適化を行ってくれます。

  1. 画像を閲覧デバイスに応じてリサイズして表示してくれる(元画像が大きくともスマホでは小さくリサイズされた画像を表示)
  2. PNGやJPEGなどの形式の画像を自動でWebPにしてくれる(しかもWebP対応ブラウザでのみWebPを読み込む)
  3. 画像にwidthheight属性を指定していても、レスポンシブ対応してくれる(画像の縦横比が崩れない)
  4. 遅延ロード(loading="lazy"属性)に対応

導入が簡単

next/image<img />要素を少し書き換えるだけで導入できます。例えば、↓ このように画像を読み込んでいたとすると…

<img src="/examle.jpg" width={400} height={200} />

以下のように書き換えるだけでOKです。

import Image from 'next/image'

<Image src="/examle.jpg" width={400} height={200} />

altなどの<img />要素の属性はnext/imageでも同じように使用できます。これだけで画像の読み込みが最適化されるとは素晴らしいですね。

画像が最適化される仕組み

では一体どのような仕組みで画像は最適化されているのでしょうか。

実はnext/imageコンポーネント自体ではリサイズやWebP化などは行われておらず、URLの生成やレスポンシブ対応だけが行われています。最適化された画像自体の生成は、ブラウザからのリクエストを受けたサーバー側で行われます。

画像の最適化自体は、以下の2つのパターンで行われます。

Next.jsのビルトインのAPIを使うパターン

こちらがデフォルトの設定です。next/imageコンポーネントは、クライアント側では以下のようなimgタグに変換されます(分かりやすくするために簡略化してます)。

<img src="/_next/image?url=画像のURL&w=サイズ&q=クオリティ" />

srcで指定されている/_next/imageは、Next.js 10〜は予め用意されているエンドポイントで、Vercelなどにデプロイすれば特に設定をしなくても利用できます。

/_next/imageがリクエストを受け取るとimageOptimizerという画像最適化のための関数が呼び出されます。

next-server/server/image-optimizer.ts →

imageOptimizerは、クエリ文字列で指定されたURLの画像を読み込み、同じくクエリ文字列で指定されたクオリティやサイズに従って最適化された画像を生成します。

生成される画像の形式は「ブラウザがWebPに対応しているかどうか」等により柔軟に変わります(Acceptヘッダーを見ているようです)。

一度生成された画像はキャッシュとして保存されるため、同じ形式・サイズの画像が何度も生成されることはありません。

外部のサービスに画像最適化を任せるパターン

Cloudinaryimgixなどの外部サービスに画像の最適化を任せることもできます。この設定はnext.config.jsで行います。

next.config.js
module.exports = {
  images: {
    loader: 'cloudinary', // Cloudinaryを使う場合
    path: 'https://res.cloudinary.com/myaccount/', // CloudinaryのURLベース
  },
}

詳しくはimage-optimization#loaderをチェックしてみてください。

なお、この記事の冒頭で紹介した画像最適化の種類は、Next.jsビルトインの仕組みを使って画像最適化を行うときの話になります。外部サービスに任せる場合だとサービスによってはWebPへの変換などはしてくれない可能性があります。

追記) next/imageのより詳しい仕組みについては下記の記事が参考になります。

https://zenn.dev/saitoeku3/articles/read-next-image

next/imageを使うときに知っておきたいこと

ここからはnext/imageを導入する前に知っておきたいことをまとめていきます。

画像最適化の全体設定はnext.config.jsで行う

画像最適化についての設定はnext.config.jsimagesプロパティで行います。ここでの設定はプロジェクト内のすべてのnext/imageコンポーネントに反映されるイメージです。

next.config.js
module.exports = {
  images: {
    // ここ
  },
}

設定項目の一覧は公式ドキュメントにまとまっています。

Image Optimization #Configuration →

外部の画像を最適化する場合はdomainsを指定しておく

例えば、Amazon S3やGoogle Cloud Storageの画像を最適化するときには、あらかじめnext.config.jsdomainsを指定しておきます(でないと開発環境でエラーが発生します)。

next.config.js
module.exports = {
  images: {
    domains: ['storage.googleapis.com'],
  },
}

Next.jsのpublicディレクトリ内の画像を読み込む場合にはこの設定は不要です。

基本的にwidthとheightを指定する

'next/image'コンポーネントでは基本的に幅(width)と高さ(height)を数字で指定するようにします。

import Image from 'next/image'

<Image src="..." width={600} height={300} />

<img />タグと同じくpx%などの単位をつけることはできませんが、next/imageではレスポンシブに画像サイズを調整してくれます。例えば親要素の幅がwidthより小さくなっても、縦横比を保ったまま画像を小さく表示してくれます。

なお<Image src="~" unsized={true} />とすることでwidthheightの指定を省略できますが、パフォーマンス等の観点からあまり使うべきではないと思います。

画質はコンポーネントごとに変えられる

画像のクオリティ(画質)はコンポーネントごとに1100の間で指定できます(デフォルトでは75)。例えば、より画質を落としたい場合は、以下のようにqualityをセットします。

import Image from 'next/image'

<Image src="..." quality={50} width={600} height={300} />

Vercelでnext/imageを使うときはLimitに注意

Next.jsのデプロイ先として真っ先に挙がるのがVercelだと思います。意外と知られていないような気がしますが、VercelにはFair Use Policyというガイドラインが決められています。


2020年10月時点

無料プラン(Hobby)の場合、使用できるBandwidthは100GB/月までです。

VercelではProductionデプロイの度にキャッシュが削除されるため、大きな画像をたくさん表示するようなアプリで何度もデプロイしていると、この制限をいつの間にか超えてしまう可能性があります。

WebP未対応のSafariでJEPG画像を読み込もうとすると逆に重くなる

Next.js 10のnext/imageによる画像最適化を試してみた
に書かれているように、以下の条件で画像サイズが逆に重くなってしまうことがあります。

  • next/imageでJPEG画像を読み込む
  • WebP未対応のSafariで表示

現時点の画像生成周りのコードを見る限り、Acceptヘッダーをもとに生成する画像のContent-Typeを決めているようです。WebP未対応のSafariではAcceptヘッダーのデフォルト値の関係から、JPEG画像を読み込む場合にPNG画像に変換されてしまいます。

これによりサイズが重くなってしまうため、現時点では注意が必要そうです[2]

imgに直接marginを指定していると効かないことがある

next/imageではレスポンシブ対応するために、<img><div>で囲ったような形で出力されます。

<div style="max-width: 100%; width: 100px;">
  <div style="position: relative; padding-bottom: 100%;">
     <img alt="react" src="/_next/image?url=hoge"  style="visibility: visible; height: 100%; left: 0px; position: absolute; top: 0px; width: 100%;">
  </div>
</div>

これゆえに<img>に対してmarginを直接当てていたりすると、marginがイメージ通りに効かなかったりします。

該当するGitHub Issue: Margin styles are creating unexpected behaviour in the new Image component #18344

これについては今後のバージョンで問題が解消されるかもしれませんし、現時点でもCSSのFlexboxなどを使えば基本的に対応可能だと思います。

脚注
  1. 厳密には、画像のリサイズなどはコンポーネント自体では行われずにサーバー上で行われます。 ↩︎

  2. next/imageではunoptimized属性をtrueにすることで画像の最適化をスキップできます。例えばJPEG画像の場合にはunoptimized={true}となるようにすれば、とりあえずの問題回避にはなります(WebP対応ブラウザでもJPEGの最適化がされなくなりますが)。 ↩︎

Discussion