概要
2020年10月27日にリリースされたNext.js 10ですが、画像表示が非常に便利になっていましたので、調査をしてみました。
※調査したバージョンは10.0.3です。
Next.js 10の画像表示とは
next/imageとしてReact Componentが用意されていて、自動的に最適化してくれます。
以前の書き方
<img src="/profile-picture.jpg" width="400" height="400" alt="Profile Picture">
Next.js 10の書き方
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分にする例
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 |
コード
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ディレクトリは作られていません。
ブラウザでアクセスすると、webpの画像ファイルができました。
開発者ツールのNetworkを見てみると、/image
というエントリーポイントに対して、パラメータが渡されています。
パラメータ | 説明 |
---|---|
url | 画像ファイルのURL。Imageのsrc。 |
w | 最適化後の横幅。 ビューポートとImageに渡されたpropsを元に、deviceSizesから最適な横幅を選択。 |
q | 最適化の品質。デフォルトが75で、Imageのpropsで設定も可能。 |
ダウンロードされている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 ※OSバージョンによってはjpegになる |
1920pxと元の横幅 | 74KBと152KB | 2回ダウンロードしてしまうバグがあるようです。 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を変更するのが良さそうです。
元画像が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/