初めに
最近ビジネスサイドからサイトの読み込みが遅い!と指摘があり改善に向けて調査&解消を進めています。
Next.jsを使ってサイトを構築しているのですが、画像を読み込む時にhtml標準のimgタグを使っていました。
調べたらNext自体にimageコンポーネントがありそちらを使うべしとたくさん記事が出てきたので
自分の理解を深めるためにまとめてみます。
<img> タグと next/image の違い
普通の <img>
- ブラウザが指定された URL の画像を そのまま 取りに行って表示する
- サイズ調整・圧縮・形式変換などの 最適化は基本されない
- width/height 未指定だとレイアウトが後からズレやすい(CLS)
Next.js の Image(next/image)
- Next.js が画像表示を 最適化するための仕組み込みで提供するコンポーネント
- 以下を自動/半自動でやる:
- レスポンシブ用の適切なサイズ配信
- 遅延読み込み(lazy load)
- レイアウト崩れ(CLS)を防ぐ
- WebP/AVIF などへの変換
Image と<img>のメリット
- パフォーマンス改善(LCP/CLS に効きやすい)
- 画像サイズを指定する設計なので CLS が起きにくい
- lazy load が標準
- 画面外の画像を後から読み込むので初期表示が軽くなる
- レスポンシブ対応がしやすい
- sizes を書けば画面幅に応じて適切なサイズを配信できる
- 最適化(圧縮・フォーマット変換)の恩恵を受けられる
- 表示しているブラウザが対応している場合は
WebPやAVIFの次世代形式を自動で返却し、対応していない場合はpngやjpgなどを自動で返却する
- 表示しているブラウザが対応している場合は
参考情報
LCP・CLSについて
https://fastest.jp/blog/largest-contentful-paint/#Cumulative_Layout_ShiftCLS
パフォーマンス改善(LCP / CLS に効きやすい)
next/image を使うと画像の「表示タイミング」と「表示サイズ」を Next.js がコントロールしてくれる
通常の <img> タグ
- 画像の実サイズは ダウンロードが始まるまで分からない
-
width/heightを省略すると、画像表示時にレイアウトがズレる
ため、CLS(Cumulative Layout Shift) の原因になります。
Image コンポーネントでは、width / height または fill の指定が必須になるため、
- 画像が読み込まれる前から 表示領域が確保される
- レイアウトが後から動かない
- CLS が発生しにくい
という状態を 設計レベルで強制できる。
また、ファーストビューに表示される重要な画像に対しては priority を指定でき、これにより LCP(Largest Contentful Paint) の改善にもつながる。
lazy load が標準で有効
next/image では、画面外にある画像は自動的に遅延読み込み(lazy load) される。
そのため、
- 初期ロード時に必要な画像だけを読み込む
- スクロールして初めて表示される画像は後回しになる
- ネットワーク負荷と初期描画コストが下がる
といった効果が得られます。
<img> タグでも loading="lazy" を指定すれば同様の挙動は可能だが、
デフォルトだと設定がOFFのため
- 書き忘れが起きやすい
- チーム開発では指定ルールが揺れがち
という問題が起こり得る。
レスポンシブ対応がしやすい
デフォルトの<img>タグだと 表示サイズは小さいのに、大きい画像をダウンロードしてしまう
という問題があります。
例)
表示領域は300*300
実際の画像は1200*1200
のようなパターン
next/image では sizes を指定することで、
- どの画面幅で
- どれくらいの表示サイズになるか
をブラウザに伝えることができる。
<Image
src="/sample.jpg"
alt="サンプル"
fill
sizes="(max-width: 768px) 100vw, 50vw"
/>
この指定があるとブラウザは、
- スマホ → 小さい画像
- PC → 大きめの画像
を 自動で選択できる。結果として、以下が期待できる。
- モバイル回線での無駄な通信量削減
- 表示速度の向上
- ユーザー体験の改善
最適化(圧縮・フォーマット変換)の恩恵を受けられる
next/image は、ブラウザが対応している画像フォーマットを見て最適な形式を返却する。
| 環境 | フォーマット |
|---|---|
| Chrome / Edge / Safari(対応環境) | WebP / AVIF |
| 未対応ブラウザ | jpg / png |
この切り替えを 開発者が意識する必要はないので、ブラウザに合わせて最適な画像形式を自動で返却することができる。
Next/Imageのデメリット
書き方が少し面倒
next/image では、以下の指定のどちらかが必須になります。(CLSを防ぐため)
width / height-
fill + 親要素でサイズ指定
そこまでがたつきを気にしないっていう場合だと過剰かもしれません。
外部画像は設定が必要
外部ドメインの画像を使う場合、next.config.jsに設定が必要。
これを忘れると画像が何も表示されなくなるので注意
// next.config.js
module.exports = {
images: {
remotePatterns: [
{
protocol: "https",
hostname: "example.com",
},
],
},
};
実際の書き方
(width / height 指定)
import Image from "next/image";
export default function Page() {
return (
<Image
src="/images/sample.jpg"
alt="サンプル画像"
width={800}
height={450}
/>
);
}
特徴
- 画像の表示サイズが明確なときはこっちを使う
向いているケース
- 固定サイズで表示する画像
- デザイン上、画像サイズが決まっている箇所
- ファーストビューのメイン画像(LCP 対象)
メリット
- Next.js が最適なサイズを判断しやすい
width / height は画像の「表示上の縦横比(アスペクト比)」を決めるための情報
- 元画像は2000×1200
- 表示は 800×450で良い
みたいなパターンの時は上記で指定すればNext.js側が自動で最適化してくれて、表示に必要なサイズを返却してくれます。
fillを使うパターン
<div style={{ position: "relative", width: "100%", height: 300 }}>
<Image
src="/images/sample.jpg"
alt="サンプル画像"
fill
sizes="(max-width: 768px) 100vw, 50vw"
style={{ objectFit: "cover" }}
/>
</div>
特徴
- 親要素のサイズに合わせて画像がフィットする
- 表示するデバイスサイズによって、NextJs側で返却してもらう画像サイズを変えられる
- 親要素で 表示領域を制御する必要がある
使い所
- カードUIのサムネイル
- レスポンシブでサイズが変わるレイアウト
注意点
- 親要素に position: relative が必須
- 親要素に高さがないと画像が表示されない
- sizes を書かないと最適化効果が弱くなる
まとめ
通常のimgタグと使い勝手が違うので最初は戸惑うかもしれませんが、基本的に使わない理由はないと思いました。
next/imageは画像の最適化を自動で行ってくれるので積極的に使っていくべきだと感じました。