Next.js 10のnext/imageについて調査してみた

# 概要

2020年10月27日にリリースされたNext.js 10ですが、画像表示が非常に便利になっていましたので、調査をしてみました。
※調査したバージョンは10.0.3です。

# Next.js 10の画像表示とは
next/imageとしてReact Componentが用意されていて、自動的に最適化してくれます。

**以前の書き方**

```html
<img src="/profile-picture.jpg" width="400" height="400" alt="Profile Picture">
```

**Next.js 10の書き方**

```tsx
import Image from 'next/image'

<Image src="/profile-picture.jpg" width="400" height="400" alt="Profile Picture">
```

# next/imageの機能について
### リサイズ
ブラウザのビューポートによって最適なファイルサイズにします。

### 画像フォーマットの変換
ブラウザのサポート状況によって、次世代画像フォーマットにします。
次世代画像フォーマットとは、WebP、JPEG2000、JPEG XRのことで、従来のJPEGやPNGに比べ、ファイルサイズが小さく、表現力が高いです。
ただ、ブラウザのサポート状況がバラバラなため、開発者としては非常に扱いづらい状況でした。
しかし、next/imageがサポートしてくれるため、開発者がフォーマット変換する必要もなく、ユーザーは早く表示ができます。

**画像フォーマットについて**
https://websas.jp/column/developer-01

**各フォーマットのサポート状況**
https://caniuse.com/#feat=webp
https://caniuse.com/#feat=jpeg2000
https://caniuse.com/#feat=jpeg+xr

### 画像が別のURLにあっても、最適化可能
画像などのリソースは画像用のサーバーに置いておくこともあります。
そういう場合でも、next/imageは使えます。
next.jsの設定ファイルでホスティングされているURLパスを設定できます。

### リクエスト時に最適化する
画像の最適化処理はビルド時ではなく、リクエスト時にします。
最適化された画像ファイルは、`.next/cache/images` ディレクトリに保存されます。

### キャッシュ
最適化された画像ファイルは有効期限が設定され、期限内であれば再利用します。
期限を過ぎていた場合は、新しく最適化する前に削除されます。

有効期限は画像配信するサーバーのレスポンスヘッダーのCache-Controlを参照しているようで、s-maxageがあればその値を、なければmax-ageを、それもなければ60秒です。

next.js内で画像を管理する場合は、next.config.jsで有効期限を変更できます。

**60秒から5分にする例**

```javascript
module.exports = {
 async headers () {
   return [
     {
      source: '/(.*).(jpg|png)',
      headers: [
        {
          key: 'Cache-Control',
          value:
            'public, max-age=300, s-maxage=300',
        },
      ],
     }
   ]
 }
}
```

### 遅延ロードがデフォルト

遅延ロードとは、ユーザーが画像を見える位置に近づいた時に、画像をダウンロードする方法です。
初期表示に見えない画像までダウンロードして、表示速度を遅くするのを避けられます。

※コードを見たところ、画像ファイルから200px以内になると、ダウンロードされるようになっていました。
https://github.com/vercel/next.js/blob/v10.0.3/packages/next/client/image.tsx#L246


# サンプルコードの起動
サンプルコードが公式で用意されているので、こちらを使いました。
https://github.com/vercel/next.js/tree/v10.0.3/examples/image-component

ダウンロードしてローカルで起動するのもありですが、こちらでも確認できます。
https://image-component.nextjs.gallery/layout-fixed

### 用意されている画像

| 画像ファイル名 | 容量 | サイズ |
| ----------- | ----------- | ----------- |
| moutain.jpg | 294KB | 2800 x 1900 |

### コード

```jsx
import Image from 'next/image'
import ViewSource from '../components/view-source'

const Fixed = () => (
  <div>
    <ViewSource pathname="pages/layout-fixed.js" />
    <h1>Image Component With Layout Fixed</h1>
    <Image
      alt="Mountains"
      src="/mountains.jpg"
      layout="fixed"
      width={700}
      height={475}
    />
  </div>
)

export default Fixed
```
### 動作確認
起動時の.nextディレクトリを見ると、imagesディレクトリは作られていません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62645/d2fe5ca0-4814-7278-6582-f7d7896f653e.png)

ブラウザでアクセスすると、webpの画像ファイルができました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62645/041db493-a14a-88b5-17cd-4238609105f8.png)

開発者ツールのNetworkを見てみると、`/image` というエントリーポイントに対して、パラメータが渡されています。

| パラメータ | 説明 |
| ----------- | ----------- |
| url | 画像ファイルのURL。Imageのsrc。 |
| w | 最適化後の横幅。<br /> ビューポートとImageに渡されたpropsを元に、[deviceSizes](https://nextjs.org/docs/basic-features/image-optimization#device-sizes)から最適な横幅を選択。 |
| q | 最適化の品質。デフォルトが75で、Imageのpropsで設定も可能。 |


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62645/2bb4cca1-5872-1ccb-9551-c45b00757b15.png)

ダウンロードされているwebpのファイルサイズは6.7KBになっています。
macのプレビューツールで横幅750pxでjpegでリサイズした場合、27KBだったので、4倍も違うことになります。
画像を多く表示するサイトだと、ページ表示速度はかなり改善できますね。

### 各ブラウザの挙動
ブラウザごとに最適な画像を提供してくれるとのことなので、いろんなブラウザで試してみました。


| ブラウザ | 画像フォーマット | 画像の横幅 | ダウンロードサイズ | 補足 |
| ----------- | ----------- | ----------- | ----------- | ----------- |
| iPhone11 Pro Safari | webp | 元の横幅 | 152KB | |
| Android Chrome | webp | 元の横幅 | 156KB |  |
| mac Chrome | webp | 750px | 18KB |
| mac Safari | webp <br />※OSバージョンによってはjpegになる | 1920pxと元の横幅 | 74KBと152KB | 2回ダウンロードしてしまうバグがあるようです。 <br />https://github.com/vercel/next.js/issues/19478 | |
| Windows IE11 | jpeg | 元の横幅 | 255KB |
| Windows Edge (Chromium)  | webp | 750px | 18KB | |
| Windows Firefox | webp | 750px | 18KB | |

widthを700で設定しているので、それに近い750pxがダウンロードされるはずですが、元の横幅でダウンロードされてしまうのは、バグでしょうか？

元の横幅でダウンロード時、パラメータのwは3840でしたが、これはdeviceSizesの最大値です。
バグが修正されるのが一番いいですが、一時的な対処方法としては、横幅が3840pxも必要なサイトはそこまで多くないと思うので、next.config.jsで[deviceSizes](https://nextjs.org/docs/basic-features/image-optimization#device-sizes)を変更するのが良さそうです。

### 元画像がpngの場合
webpの話になってしまいますが、pngも気になったので、いくつか画像を集めて最適化の効果を確認してみました。
画像の横幅は変えていません。

| 元サイズ | webp後のサイズ |
| ----------- | ----------- |
| 242KB | 27KB |
| 239KB | 44KB |
| 23KB | 4KB |
| 176KB | 31KB |
| 84KB | 24KB |

画像によりますが、約4分の1〜9分の1に減りました。
pngもかなりサイズダウンできますね。


# なぜnext/imageが作られたのか

Next.js公式ドキュメントでCLSが触れられていることもあり、Core Web Vitalsを考慮している可能性が高いです。
Core Web VitalsとはLCP、FID、CLSという3つのパフォーマンス指標で、大きいサイズの画像ダウンロードや、画像が遅れて表示された時にレイアウト位置がずれると、悪い影響が出てしまいます。
Googleが2021年5月から検索ランキングの指標に含めると発表したこともあり、とても重要な指標になっています。

**Core Web Vitalsについて**
https://www.sakurasaku-labo.jp/blogs/core-web-vitals

# まとめ

next/imageは機能豊富で便利ですが、Propsも多く少しだけ学習が必要になります。
2021年5月にはCore Web Vitalsの影響が出てきてしまうので、Next.jsを使っている場合、早めにバージョン10を取り込むのがいいと思います。

# 参考ページ
* Image Componentについて  
https://nextjs.org/docs/basic-features/image-optimization
* Image ComponentのAPI仕様  
https://nextjs.org/docs/api-reference/next/image
* Image Component サンプルサイト  
https://image-component.nextjs.gallery/
* Image Component サンプルコード  
https://github.com/vercel/next.js/tree/v10.0.3/examples/image-component
* PNG画像素材  
https://ja.pngtree.com/