🖼️

next/imageを仕事で使う際に気をつけたい仕様

2021/10/17に公開

気をつけたい挙動

widthheight で指定した値が画像の大きさであるとは限らない

※画像自体の大きさを「画像の大きさ」として、表示する大きさを「表示領域」とします

例えば、以下のような実装をしたとします。
( next.config.js で next/image の設定を変えていないデフォルトの状態)

<Image
  src="hoge.png"
  alt="hogehuga"
  width={500}
  height={500}
/>

この場合、画像の大きさは 500x500 px になると思いませんか?

ならないのです!!

この実装で得られる実際の画像の大きさは 1080x1080 px になります。

意図していない大きさの画像を表示することによる懸念

個人で開発している分には意図せず画像が大きくても問題ないかもしれません。しかし、仕事で使う場合は以下の懸念が発生します。

  • 通信量の増加によってサイトのパフォーマンスが悪化
  • 画像の表示速度が遅い
  • next/image の loader に外部サービスを指定していた場合は画像のファイルサイズ増加によって料金が増加する可能性がある
  • 指定した loader によっては画像のオリジナルの大きさよりも大きなサイズに拡大される

(上記の上2つは next/image のデフォルト loader を使っている場合はブラウザが対応していれば WebP に変換してくれるので懸念が軽減される)

仕様の詳細

では、なぜ上記のような挙動になるのか説明します。

この挙動は next/image の最適化処理の仕様によって引き起こされます。next/image は画像の表示領域に合わせて画像の大きさを適切な大きさに変化させます。さらに、単純に表示領域に合わせているのではなく、高解像度ディスプレイ( Retina, 4K など)でも画像が綺麗に表示されるように、指定された widthheight より大きな画像を取得します。取得する画像の大きさを決定するために使われるのが、next/image が保持しているブレイクポイントです。

最初に紹介した例の解説をします。

<Image
  src="hoge.png"
  alt="hogehuga"
  width={500}
  height={500}
/>

まず、layout は指定されていないのでデフォルトの intrinsic になります。intrinsic は指定された widthheight のアスペクト比を維持します。intrinsic の場合、指定した width 2倍以上で最小のブレイクポイントを取得します。

つまり、取得される画像の大きさは、

500 x 2 = 1000
1000以上で最小のブレイクポイントは1080
横幅が1080px
アスペクト比は1:1なので縦幅も1080px
したがって、1080x1080 px の画像が取得される

という挙動になります。

指定した通りの大きさの画像を常に取得する方法

next/image に unoptimized という Props を指定します。この Props を指定することで next/image による画像の最適化が行われなくなります。

しかし、要件によってはこれだけでは解決しない場合があります。画像サイズは固定にしつつ独自の画像の最適化を行いたい場合です。

公式サイトでは以下のように loader をカスタマイズする方法が紹介されています。

import Image from 'next/image'

const myLoader = ({ src, width, quality }) => {
  return `https://example.com/${src}?w=${width}&q=${quality || 75}`
}

const MyImage = (props) => {
  return (
    <Image
      loader={myLoader}
      src="me.png"
      alt="Picture of the author"
      width={500}
      height={500}
    />
  )
}

この実装だと、myLoaderwidth はやはり 1080 になってしまいます。

そこで以下のような実装にすることで、画像サイズを固定しつつ独自の画像の最適化を行うことが可能です。

import Image from 'next/image'

const myLoader = ({ src, width, quality }) => {
  return `https://example.com/${src}?w=${width}&q=${quality || 75}`
}

const MyImage = (props) => {
  return (
    <Image
-     loader={myLoader}
-     src="me.png"
+     src={myLoader({src: "me.png", width: 500})}
      alt="Picture of the author"
      width={500}
      height={500}
+     unoptimized
    />
  )
}

画像の大きさについて

仕事で画像の大きさを画面サイズ関係なく固定したいという要件があり、next/image について改めて調べたことを書いてみました。

高解像度ディスプレイに対応するために画像領域の2倍以上の大きさの画像を取得するのは理論的には良いのかもしれませんが、一方で画質は見る人が許容できるかという感覚的な要素が大きいので、どこまで画像を大きくするかは検討の余地があると思います。

そもそも、オリジナル画像が高解像度に対応できる大きさであるとは限らなかったりします。例えば、next/image のブレクイポイントの最大値は 3840 px なので正方形なら 3840x3840 px、その指定になるオリジナル画像は 3840x3840 px 以上の大きさが必要になります。自前で用意する画像ならまだしも、ユーザーが投稿した画像とかだと大きさが 3840x3840 px ないことも多いと思います。

という感じで、next/image はとても便利なコンポーネントなのですが、仕事で使う場合は気をつけないといけないよってお話でした。

Discussion