

@ 酒井悠宇
今日はnext/imageについて調べていきたいと思います。
nextで画像を自由に扱えるようになりたい!!
https://www.forcia.com/blog/001561.html
「 next/imageとは、画像サイズと拡張子をデバイスとブラウザに応じて最適な形で出し分けてくれるReactコンポーネントのこと」
画像ファイルは一般的なWebページの全体のバイト数の半分を占める。最適化されていない画像の送受信や描画はページ表示の遅れにつながり、UX(ユーザーエクスペリエンス)の悪化につながる。
ECサイト等のように、多くの商品画像を表示する必要があるウェブサイトの場合、この問題は特に重要。
next/imageのRFC等では、最適化されていな画像とは何かと、そのUXへの悪影響について、以下の点が指摘されている。
RFCとは?
https://gimo.jp/glossary/details/rfc.html
RFCとは、インターネットに関する仕様や規則等を定めた文書であり、インターネットにおける技術標準を示すための文書です。RFCはIETFというインターネット関連の技術を標準化するための組織によって策定され、定期的に発行されています。名前はRequest for Commentsの略称であり、この言葉は「コメントを募集しています」という意味です
例えば、スマホの画面に100 x 100pxの画像を<img>タグで表示したいとする。この時、大きすぎるサイズの画像500 x 500pixelの画像を送ってしまうと、無駄な通信が発生して画面描画が送れる。これは全体的なUXの低下につながる。
モダンな拡張子、例えばwebpはjpeg.pngに比べて30%程度軽量。従って、jpeg、png画像をこれらの拡張子に置き換えることで通信量の削減ができる。
初期描画でページ内の全ての画像を読み込むと、表示にかかる時間が不必要に伸びてしまう。速度と表示を両立させるためには、初期描画ではviewport内の画像のみを、それ以外の画像は、viewportが近づいたタイミングで順次読み込む。(遅延ローディング)
これらの課題は有名な対応法がある。例えば画像サイズの最適化に関しては、<img>タグでsrcsetを設定すれば、ブラウザが複数の画像から最適なサイズの画像を読み込みませるようにすることができる。
拡張子の最適化は、jpeg, pngを片っぱしからwebpに変換すれば対応できる。
遅延ローディングに関しても、intersection observerを使った実装などがよく知られている。
しかし、上記の画像の最適化はweb全体を見ると十分に浸透しているとはいえない。
これには以下の理由が考えられる。
上記のモダンな拡張子webpなどは、一部のブラウザではサポートされていないため、これらのブラウザのサポートと画像拡張子の最適化を同時にしようとすると、ブラウザを見て返却する拡張子を変化させる必要がある。
また、遅延ローディングにはさまざまな実装方法が知られているが、実装方法によってはうまく機能しないブラウザなどもあり注意が必要。また、ブラウザによって画像サイズを出し分ける場合、素朴には各画面に対してsrcsetと各サイズの準備をする必要があり、実装の手間がかかる。
外部サーバーから取得した画像を最適化する場合、外部サーバーから画像を取得し、画像の最適化をしてブラウザに返却するような中間のサーバーが必要になる。
画像の最適化に真剣に取り組もうとするとこれらの課題をクリアしながら開発する必要がある、そのコストは決して少なくない。
これらの解決策として登場したのがnext/image。
next/imageの導入は非常に簡単。タグをReactコンポーネントに置き換えるだけで画像サイズと拡張子の適切な出し分けができるようになる。付属ライブラリなどのインストールなども不要。
// Case A. 通常の画像コンポーネント
const UnoptimizedImage = () => <img src="{`/river.jpg`}" width="{360}" height="{240}" />;
// Case B. next/iamgeを使った画像コンポーネント
import Image from "next/image";
const OptimizedImage = () => (
// <img="" />を <Image />に置き換えるだけ!!
<Image src={`/river.jpg`} width={360} height={240} />
);
<img>を使った場合も<Image>コンポーネントを使った場合も同じ画像が表示される。一方で、画像取得のリクエストや生成されるDOM要素は大きく異なり、<Image>コンポーネントを使った場合、public配下に何の最適化もしていない画像を配置しても、最適な画像サイズが最適な拡張子で遅延読み込みされるようになる。
<Image>コンポーネントから生成されるDOM要素は以下のようになる。<img>に加えて<img>をラップするようなDOM要素が生成される。
// Case A. 通常の画像コンポーネントから生成されるDOM要素
<img src="/river.jpg" width="360" height="240" />
// Case B. Imageコンポーネントから生成されるDOM要素
<!-- レイアウトを整える用のラッパーDOM要素 -->
<div
style="
display: inline-block;
max-width: 100%;
overflow: hidden;
position: relative;
box-sizing: border-box;
margin: 0;
"
>
<div style="box-sizing: border-box; display: block; max-width: 100%">
<img
style="max-width: 100%; display: block"
alt=""
aria-hidden="true"
role="presentation"
src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzYwIiBoZWlnaHQ9IjI0MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2ZXJzaW9uPSIxLjEiLz4="
/>
</div>
<!-- 画像のDOM要素 -->
<!-- srcが/_next/image/下のパスに置き換わり、decoding, srcsetが設定されている ! -->
<img
src="/_next/image?url=%2Friver.jpg&w=1080&q=75"
decoding="async"
style="
visibility: visible;
position: absolute;
inset: 0px;
box-sizing: border-box;
padding: 0px;
border: none;
margin: auto;
display: block;
width: 0px;
height: 0px;
min-width: 100%;
max-width: 100%;
min-height: 100%;
max-height: 100%;
"
srcset="
/_next/image?url=%2Friver.jpg&w=384&q=75 1x,
/_next/image?url=%2Friver.jpg&w=750&q=75 2x,
/_next/image?url=%2Friver.jpg&w=1080&q=75 3x
"
/>
<Image>コンポーネントにおける画像取得の仕組みは大まかに以下のようになっている。
<Image> を使うことにより、画像のサイズ・拡張子・読み込みタイミングの最適化と言う冒頭で挙げた3つの問題が解消されていることがわかる。
また、/_next/imageにできる画像サーバーにはキャッシュ機能があり、変換されたwebp画像が.next/cache/images/配下に保持され、同じリクエストに対してはwebpへの変換なしでクライアントに返却される、ブラウザに画像がある場合は/_next/imageでバリデーションした後に304コードを返却することでブラウザのキャッシュを利用する、などのようなことをしている。
「304 (Not Modified)」とは、リクエストしたリソースが更新されていないことを示すステータスコードです。これでWebサイトが表示されている場合は、ブラウザ内のキャッシュに残っているコンテンツを使ってWebページを表示していることになります。ちなみに、キャッシュとは一度アクセスしたサイトのデータを一時的に保管しておく仕組みです。
<Image>はブラウザに応じて、適切に拡張子を選んでくれる。
例えば、webpに対応しているChrome, Firefoxに対してはwebpが、webp非対応のIEに対してはjpegがそのまま返却される。
<Image>コンポーネントには豊富なオプション引数が存在する。
<Image
src={`/river.jpg`} // ソースファイル, string
width={420} // 表示幅, number
height={280} // 表示高さ, number
quality={75} // 画質, number
priority={false} // 表示の優先度, boolean
loading={"lazy"} // 遅延ロードするかどうか, "lazy" | "eager"
unoptimized={false} // 最適化するかどうか, boolean
layout={"fixed"} // レイアウト, "fill" | "fixed" | "intrinsi| "responsive"
objectFit={"contain"} // layout='fill'の場合のobject-fit
objectPosition={"50% 50%;"} // layout='fill'のobject-position
/>
これらの他に、例えば画像のalt属性など、<img>タグに設定できる属性は<image>コンポーネントのpropsとして設定することができる。但し、style, srcSet, decodingは例外で、設定したとしても<Image>コンポーネント内の<img>タグのpropsを設定する際に上書きされてしまう。
1.src: 画像のソースファイル
2.width: 画像の幅
3.height: 画像の高さ
widthやheightは通常の<img>タグと異なり、必須の引数となっている。widthやheightの設定されていない画像は、CLSの悪化を引き起こすが、<Image>コンポーネントでは開発者が自然とそれを避けられるように設計されている。
CLSとは、 Common Language Specification(共通言語仕様)のこと。
1.priority: preloadするかどうかのフラグ
2.loading: 遅延ローディングをするかどうかのフラグ
上で説明したように、デフォルト設定では画像が遅延ロードされるが、遅延ロードを有効にしたくないケースもある。例えば、サイズが大きくローディングに時間がかかる画像や、トップページのヒーローイメージのようにファーストビューですぐに表示したい画像などだ。これらのケースではpreload=true や loading='eager' の設定が有効。
1. unoptimized: 最適化するかどうかのフラグ
2.quality: 画質
qualityによって画像サイズが劇的に変化するため、このオプションは背景画像など、サイズが大きくローディング時間を短縮したい状況で使えそう。unoptimized=trueと設定するべき状況として、RFCでは、next/imageに対応していないloaderの画像を取得する場合などが挙げられている。公式ドキュメント
<Image>コンポーネントでは、画像のレイアウトに対しても豊富なオプションが提供されている。
1.layout: viewportを変更した時のレイアウトを表す
2.obujectFit, objectPosition:
はい、と言うわけで今日はnext/imageについて色々調べてみました。
ただ何となく何となく使っていたんですが、next/imageがどんなことをしているのかが雰囲気わかりました。
最後までごらんいただきありがとうございました!