Next.jsのImageコンポーネントによる画像表示のパフォーマンス最適化

## 目標
* 画像を用いたウェブサイトにおけるパフォーマンスの問題やSEOへのダメージを理解する
* Next.jsのImageコンポーネントによってその問題をどのように解決できるかを理解する。


## ウェブサイトにおける画像表示の問題
参考：[Images on the Web](https://nextjs.org/blog/next-10#images-on-the-web) より

* Webページで表示されるコンテンツのデータ量の50%は画像である。そしてこれはLCP[^1]に大きく影響する。このLCPの値は近々Googleの検索結果のランキングに大きく影響するようになる値である。
* Webサイトで表示される画像の半分以上が1MB以上であり、これはウェブサイトの表示に適したサイズの物ではない。(PCのブラウザ、タブレット、スマホなど様々なデバイス上で画像は表示されるが、たとえ表示領域が100pxしかないとしても200pxの画像は2000pxのサイズでロードされるためだ。)さらに、30%以上の画像はファーストビューには表示されない。
* 多くの場合画像はwidthやheightのプロパティを持っておらず、ページがロードされた際に、ジャンプをひきおこしてしまうため、CLSの値を悪くする。 [^2]
* 99.7%以上のwebサイトがWebPのようなモダンな画像フォーマットを使用していない。
* また、サイズ、重さ、レイジーローディング、モダンな画像フォーマット開発者がこれらの問題に対処する際に考えなければならない点についても膨大にある。そういった要素に対応するために開発者は画像の最適化に合わせた複雑なビルドツールのセッティングをすることになるわけである。しかしながら、これらの最適化は外部サイトのソースなど、ユーザーがサブミットした画像には適用されず、全ての画像について最適化するのは不可能に近い。このような開発タスクの困難さは必然的にUXを悪くすることになる。

**まとめると、ウェブサイトにおける画像の取り扱いにはこのようにUXやSEO、そして開発の対応など、考えるべきことがたくさんあります。**

## \<Image /> コンポーネント
* そのような問題を解決するために、Next.jsがImageコンポーネントを用意しました。用法は以下の通りです。

```next.config.js
module.exports = {
  images: {
    domains: ['example.com'], // 画像のホスティング先のdomainの配列
  },
}
```
```tsx:pages/index.tsx
import Image from 'next/image'

const Home: React.FC = () => {
  return (
    <>
      <h1>My Homepage</h1>
      <Image
        src="/me.png"
        alt="Picture of the author"
        layout={"responsive"}
        width={500}
        height={500}
      />
      <p>Welcome to my homepage!</p>
    </>
  )
}

export default Home
```

このコンポーネントの便利なところは、既存の `<img />` 要素を置き換えるだけで良いという点が挙げられます。(実際設定している内容はほぼ同じ)。

### プロパティ
* width, height
    * layoutがfill以外の場合は必須。pixelで指定。
* layout
    * `fixed`: `<img />`と同じで、viewportの変化に影響されない
    * `intrinsic`: layoutのデフォルト値。viewportの小さいデバイスの場合、画像の寸法を小さくする。viewportが大きい場合は、オリジナルの寸法を用いる
    * `responsive`: iewportの小さいデバイスの場合、画像の寸法を小さくする。viewportが大きい場合は、寸法も大きくする。
    * `fill`: 親要素の大きさに応じて、幅も高さも変化する,多くの場合object-fitと併用される。

### Imageコンポーネントの行っている最適化処理

* ユーザーが画像に近づいたら画像のローディングを行う(これによって、30%以上のコンテンツをページの初期ローディング時にダウンロードする必要がなくなる)
* 指定されたwidthやheightをそのまま固定値として使用するのではなく、そのアスペクト比に基づいて自動的にレスポンスデザインとすることが可能。よって、CLSの改善とレスポンシブデザインの両方を意識する必要がなくなる。
* ファーストビューに入る画像をプリロードすることによってLCPを50%近く改善することができる。
* データのソースによらず、全ての画像についてこれらの最適化を行うことが可能となる。
* 画像コンテンツのキャッシュ



## Next.jsコンフィグの設定

```next.config.js
module.exports = {
  images: {
    loader: 'imgix', // デフォルトはundefined
    path: 'https://example.com/myaccount/', // 必ず指定
    deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840], // デフォルト値
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], // デフォルト値
  },
}
```
* loader
    * imgix, cloudinary, akamai(現状ではこの3つ)などの画像ホスティングサービスを使用している場合は、そちらの最適化をそのまま使用することができるため、ここで指定します。デフォルトではNext.jsのローダーによって最適化は可能なので特に指定する必要はありません。
* domain
    * 画像コンテンツをホスティングしているサービスなどのドメインを指定します。
* deviseWidth
    * ユーザーが使用するデバイスのwidthがわかっている場合は、ここで指定します。これは、`layout="responsive"` または `layout="fill"`をImageコンポーネントに渡した場合に使用されます。(srcset[^3]に指定したデバイス用の画像のURLが渡されます。)
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/220907/388450bb-fdc9-dd17-dfc1-68df0ec91322.png)

* imageWidth
    * deviseWidthとは違う値(小さい値)を指定します。 `layout="fixed"` または `layout="intrinsic"`を指定された場合に、有効になります。(deviseWidthと同様にsrcsetにURLが渡されます。)
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/220907/a86901ed-0304-b73a-117b-5088e4757f68.png)



## まとめ
* 画像の問題考えるべきことがたくさんありますが、このようにコンポーネント一つで大体解決してくれるのは嬉しいですね。next.jsは色々と便利な機能がたくさんあるので、ぜひ使ってみてください。

## 参考
* [Images on the Web](https://nextjs.org/blog/next-10#images-on-the-web)
* https://web.dev/vitals/

[^1]: Largest Contentful Paint = ファーストビューにおいて占める割合が大きいコンテンツ(画像やテキストなど)が表示されるタイミングのこと。何が重要であるかは、標準化が難しいが、UXに繋がる指標と考えられている。
[^2]: Cumulative Layout Shift = 累積レイアウト変更のこと。ブラウザは画像のダウンロードを待たずに、描画を始めるため、img要素は最初は0x0とみなして、描画した後、実際の画像が入ってきたら、その大きさになるため、不追加のレイアウト変更が発生してしまう。これは、widthとheightをimg要素に直接指定することで回避可能だが、そうすると、レスポンシブに対応させることが難しくなる。
[^3]: ブラウザのスクリーン要件(幅、高さ、ピクセル密度)に応じて、異なる画像を読み込む事ができる物です。より詳しくは[srcset属性について](https://qiita.com/C058/items/643a9ff2d23dfe3a0b37)