画像コンポーネントはパフォーマンスに関するベスト プラクティスをカプセル化し、画像を最適化するためのすぐに使えるソリューションを提供します。
画像は、ウェブ アプリケーションのパフォーマンス ボトルネックの一般的な原因であり、最適化の重要な重点分野です。最適化されていない画像はページの肥大化につながるため、90 パーセンタイルthでページの合計重量(バイト単位)の 70% 以上を占めています。画像を最適化する方法が複数ある場合は、パフォーマンス ソリューションがデフォルトで組み込まれたインテリジェントな「画像コンポーネント」が必要です。
Aurora チームは、Next.js を使用して、そのようなコンポーネントの一つを構築しました。ウェブ デベロッパーがさらにカスタマイズできる最適化されたイメージ テンプレートを作成することが目的でした。このコンポーネントは優れたモデルとして機能し、他のフレームワーク、コンテンツ マネジメント システム(CMS)、技術スタックで画像コンポーネントを作成するための基準を確立します。Google は、Nuxt.js のコンポーネントの開発に協力しており、今後のバージョンでは Angular と連携して画像の最適化に取り組んでいます。この記事では、Next.js Image コンポーネントの設計方法と、その過程で得た教訓について説明します。
画像の最適化に関する問題と改善の余地
画像はパフォーマンスだけでなく、ビジネスにも影響します。ページ上の画像の数は、ウェブサイトにアクセスしたユーザーのコンバージョンの予測力で 2 番目に高い項目でした。ユーザーがコンバージョンに至ったセッションでは、コンバージョンに至らなかったセッションと比べて画像が 38% 少ないことがわかりました。Lighthouse のベスト プラクティス診断では、画像を最適化してウェブに関する主な指標を改善するための複数の最適化案が示されます。画像がウェブに関する主な指標とユーザー エクスペリエンスに影響する可能性がある一般的な領域は次のとおりです。
サイズのない画像は CLS に悪影響を及ぼす
サイズを指定せずに画像を配信すると、レイアウトが不安定になり、Cumulative Layout Shift(CLS)が高くなる可能性があります。img 要素の width
属性と height
属性を設定すると、レイアウト シフトを防止できます。例:
<img src="flower.jpg" width="360" height="240">
レンダリングされた画像のアスペクト比が自然なアスペクト比に近くなるように、幅と高さを設定する必要があります。アスペクト比の違いが大きい場合、画像が歪んで見えることがあります。CSS の aspect-ratio を指定できる比較的新しいプロパティを使用すると、CLS を防ぎながら画像のサイズをレスポンシブに設定できます。
大きな画像は LCP を低下させる可能性がある
画像のファイルサイズが大きいほど、ダウンロードに時間がかかります。大きな画像は、ページの「ヒーロー」画像や、Largest Contentful Paint(LCP)をトリガーするビューポート内の最も重要な要素である可能性があります。重要なコンテンツの一部であり、ダウンロードに時間がかかる画像は、LCP の遅延につながります。
多くの場合、デベロッパーは、より優れた圧縮とレスポンシブ画像の使用によって画像サイズを削減できます。<img>
要素の srcset
属性と sizes
属性を使用すると、さまざまなサイズの画像ファイルを提供できます。ブラウザは、画面のサイズと解像度に応じて適切なものを選択します。
画像の圧縮が不十分だと LCP が低下する
AVIF や WebP などの最新の画像形式は、一般的に使用されている JPEG や PNG 形式よりも圧縮率が高く、圧縮率を高めると、同じ画質でファイルサイズを 25 ~ 50% 削減できる場合があります。これにより、データ使用量を抑えながら、より高速にダウンロードできるようになります。アプリは、これらの形式をサポートするブラウザに最新の画像形式を配信する必要があります。
不要な画像の読み込みが LCP に悪影響を及ぼす
スクロールしなければ見えない範囲にある画像、またはビューポートにない画像は、ページの読み込み時にユーザーに表示されません。遅延させることで、LCP に影響を与えず、遅延させることができます。遅延読み込みを使用すると、ユーザーがスクロールして画像が表示されたときに、その画像を読み込むことができます。
最適化の課題
チームは、前述の問題に起因するパフォーマンス コストを評価し、それらを克服するためのベスト プラクティス ソリューションを実装できます。しかし、実際にはそうはならないことが多く、非効率的な画像がウェブの速度低下を引き続き招いています。これには次の理由が考えられます。
- 優先順位: ウェブ デベロッパーは通常、コード、JavaScript、データの最適化に重点を置いています。そのため、画像の問題や画像の最適化方法に気付いていない可能性があります。デザイナーが作成した画像やユーザーがアップロードした画像は、優先順位の高いものにならない場合があります。
- すぐに使えるソリューション: たとえデベロッパーが画像最適化のニュアンスを認識していても、フレームワークや技術スタックにオールインワンのすぐに使えるソリューションがないことは抑止力になる可能性があります。
- 動的画像: 動的画像は、アプリケーションに含まれる静的画像に加えて、ユーザーがアップロードするか、外部データベースまたは CMS から取得します。画像のソースが動的である場合、そのような画像のサイズを定義するのは難しい場合があります。
- マークアップのオーバーロード: 画像サイズやサイズ別の
srcset
を含めるソリューションでは、画像ごとに追加のマークアップが必要になるため、手間がかかります。srcset
属性は 2014 年に導入されましたが、現在使用されているウェブサイトの 26.5%のみです。srcset
を使用する場合、デベロッパーはさまざまなサイズの画像を作成する必要があります。just-gimme-an-img などのツールを使用することもできますが、画像ごとに手動で使用する必要があります。 - ブラウザのサポート: AVIF や WebP などの最新の画像形式では、作成される画像ファイルのサイズは小さくなりますが、対応していないブラウザでは特別な処理が必要になります。すべてのブラウザに画像を配信するには、コンテンツ ネゴシエーションや
<picture
> 要素などの戦略を使用する必要があります。 - 遅延読み込みのウォッチフェイスの追加機能: 折りたたみ式の画像の遅延読み込みを実装するには、複数の手法とライブラリを使用できます。最適なものを選ぶのは難しい場合があります。また、デベロッパーは、遅延読み込み画像を読み込むのに最適な「折り目」からの距離を把握していない可能性があります。デバイスによってビューポートのサイズが異なると、さらに複雑になります。
- 変化する状況: ブラウザがパフォーマンスを向上させる新しい HTML や CSS の機能をサポートし始めると、デベロッパーがそれらを個別に評価するのは困難になる可能性があります。たとえば、Chrome ではオリジン トライアルとしてフェッチ優先度機能が導入されています。ページ上の特定の画像の優先度を上げるために使用できます。全体的に、このような機能強化はコンポーネント レベルで評価、実装するのがデベロッパーにとって簡単です。
ソリューションとしての画像コンポーネント
画像を最適化する機会と、アプリケーションごとに個別に画像を実装することの課題から、画像コンポーネントにたどり着きました。イメージ コンポーネントは、ベスト プラクティスをカプセル化して適用できます。<img>
要素を画像コンポーネントに置き換えることで、デベロッパーは画像のパフォーマンスの問題をより適切に解決できます。
Google は昨年、Next.js フレームワークを使用して、画像コンポーネントを設計し、実装しました。次のように、Next.js アプリの既存の <img>
要素のドロップイン リプレースメントとして使用できます。
// Before with <img> element:
function Logo() {
return <img src="/logo.jpg" alt="logo" height="200" width="100" />
}
// After with image component:
import Image from 'next/image'
function Logo() {
return <Image src="/logo.jpg" alt="logo" height="200" width="100" />
}
このコンポーネントは、豊富な機能と原則を通じて、画像関連の問題に汎用的に対処しようとします。また、デベロッパーがさまざまな画像要件に合わせてカスタマイズできるオプションも用意されています。
レイアウトのずれからの保護
前述のように、サイズが指定されていない画像はレイアウトのずれを生じさせ、CLS の増加につながります。Next.js Image コンポーネントを使用する場合は、レイアウトのずれを防ぐために、width
属性と height
属性を使用して画像サイズを指定する必要があります。サイズが不明な場合、デベロッパーは layout=fill
を指定して、サイズ調整されたコンテナ内にあるサイズ設定されていない画像を提供する必要があります。または、静的イメージのインポートを使用して、ビルド時にハードドライブ上にある実際のイメージのサイズを取得し、イメージに含めることができます。
// Image component with width and height specified
<Image src="/logo.jpg" alt="logo" height="200" width="100" />
// Image component with layout specified
<Image src="/hero.jpg" layout="fill" objectFit="cover" alt="hero" />
// Image component with image import
import Image from 'next/image'
import logo from './logo.png'
function Logo() {
return <Image src={logo} alt="logo" />
}
デベロッパーはサイズ指定のない Image コンポーネントを使用できないため、デベロッパーは画像のサイズを検討し、レイアウト シフトを防ぐために時間を確保できます。
迅速な対応
デバイス間で画像をレスポンシブにするには、<img>
要素に srcset
属性と sizes
属性を設定する必要があります。この作業を Image コンポーネントで軽減したいと考えました。Next.js Image コンポーネントは、アプリケーションごとに属性値を 1 回だけ設定するように設計されています。レイアウト モードに基づいて、Image コンポーネントのすべてのインスタンスに適用されます。そこで、
deviceSizes
プロパティ: このプロパティを使用すると、アプリのユーザーベースに共通するデバイスに基づいて、ブレークポイントを 1 回だけ構成できます。ブレークポイントのデフォルト値は構成ファイルに含まれています。imageSizes
プロパティ: これは、デバイスのサイズのブレークポイントに対応する画像サイズを取得するために使用される、構成可能なプロパティです。- 各画像の
layout
属性: 各画像のdeviceSizes
プロパティとimageSizes
プロパティの使用方法を指定します。レイアウト モードでサポートされている値は、fixed
、fill
、intrinsic
、responsive
です。
レイアウト モードが レスポンシブまたは フィットで画像がリクエストされた場合、Next.js はページをリクエストしたデバイスのサイズに基づいて配信する画像を特定し、画像の srcset
と sizes
を適切に設定します。
次の比較は、レイアウト モードを使用して、さまざまな画面で画像のサイズを制御する方法を示しています。Next.js ドキュメントで共有されているデモ画像をスマートフォンと標準のノートパソコンで表示しています。
ノートパソコンの画面 | 電話による選抜 |
---|---|
レイアウト = 組み込み: 小さいビューポートでコンテナの幅に合わせて縮小されます。ビューポートが広い場合でも、画像の元のサイズを超えて拡大されません。コンテナの幅が 100% です | |
レイアウト = 修正済み: 画像がレスポンシブではありません。幅と高さは、レンダリングされるデバイスに関係なく、 要素と同様に固定されます。 | |
レイアウト = レスポンシブ: アスペクト比を維持しながら、さまざまなビューポートのコンテナの幅に応じてスケールダウンまたはスケールアップします。 | |
レイアウト = 塗りつぶし: 親コンテナに合わせて幅と高さが引き伸ばされます。(この例では、親 <div> の幅は 300*500 に設定されています) |
|
遅延読み込みを組み込む
Image コンポーネントには、デフォルトで高性能の遅延読み込みソリューションが組み込まれています。<img>
要素を使用する場合、遅延読み込みにはいくつかのオプションがありますが、どれも使いにくい欠点があります。デベロッパーは、次のいずれかの遅延読み込みアプローチを採用できます。
loading
属性を指定します。これはすべての最新ブラウザでサポートされています。- Intersection Observer API を使用する: カスタムの遅延読み込みソリューションを構築するには、労力と慎重な設計と実装が必要です。デベロッパーは、常にこの作業を行う時間があるわけではありません。
- サードパーティ ライブラリをインポートして画像を遅延読み込みする: 遅延読み込みに適したサードパーティ ライブラリを評価して統合するには、追加の作業が必要になる場合があります。
Next.js Image コンポーネントでは、デフォルトで読み込みが "lazy"
に設定されています。遅延読み込みは Intersection Observer を使用して実装されます。これは、最新のほとんどのブラウザで利用可能です。デベロッパーは、この機能を有効にするために特別な操作を行う必要はありませんが、必要に応じて無効にできます。
重要な画像をプリロードする
LCP 要素は多くの場合画像であり、大きな画像は LCP を遅らせる可能性があります。ブラウザがその画像をより早く検出できるように、重要な画像をプリロードすることをおすすめします。<img>
要素を使用する場合は、次のように HTML ヘッダーにプリロード ヒントを含めることができます。
<link rel="preload" as="image" href="important.png">
適切に設計された画像コンポーネントでは、使用しているフレームワークに関係なく、画像の読み込み順序を調整する方法を提供する必要があります。Next.js Image コンポーネントの場合、デベロッパーは images コンポーネントの priority
属性を使用して、プリロードに適した画像を指定できます。
<Image src="/hero.jpg" alt="hero" height="400" width="200" priority />
priority
属性を追加すると、マークアップが簡素化され、使いやすくなります。画像コンポーネントのデベロッパーは、特定の条件を満たすページ上の折り返し上部の画像のプリロードを自動化するためにヒューリスティクスを適用するオプションを検討することもできます。
高パフォーマンスの画像ホスティングを推奨する
画像の最適化を自動化するには、画像 CDN を使用することをおすすめします。また、WebP や AVIF などの最新の画像形式もサポートしています。Next.js Image コンポーネントは、デフォルトでローダー アーキテクチャを使用して画像 CDN を使用します。次の例は、ローダーによって Next.js 構成ファイルで CDN を構成できることを示しています。
module.exports = {
images: {
loader: 'imgix',
path: 'https://ImgApp/imgix.net',
},
}
この構成では、デベロッパーは画像ソースで相対 URL を使用できます。フレームワークは、相対 URL を CDN パスと連結して絶対 URL を生成します。Imgix、Cloudinary、Akamai などの一般的な画像 CDN がサポートされています。このアーキテクチャでは、アプリにカスタム loader
関数を実装することで、カスタム クラウド プロバイダの使用をサポートしています。
セルフホスト イメージをサポートする
ウェブサイトで画像 CDN を使用できない場合があります。このような場合は、イメージ コンポーネントがセルフホスト イメージをサポートしている必要があります。Next.js Image コンポーネントは、CDN のような API を提供する組み込みの画像サーバーとして画像オプティマイザーを使用します。オプティマイザは、サーバーにインストールされている場合、本番環境の画像変換に Sharp を使用します。このライブラリは、独自の画像最適化パイプラインの構築を検討しているすべての方に適しています。
プログレッシブ ローディングをサポートする
プログレッシブ ローディングは、実際の画像の読み込み中に、通常は画質が大幅に低いプレースホルダ画像を表示することで、ユーザーの関心を維持する手法です。パフォーマンスの認識を高め、ユーザー エクスペリエンスを向上させます。折り返しの下にある画像や折り返しの上にある画像に対して、遅延読み込みと組み合わせて使用できます。
Next.js Image コンポーネントは、プレースホルダ プロパティを使用して画像の段階的な読み込みをサポートしています。これは LQIP(低画質画像プレースホルダ)として使用でき、実際の画像の読み込み中に低画質またはぼやけた画像を表示できます。
影響
こうした最適化をすべて取り入れたことで、本番環境における Next.js の Image コンポーネントでは成功を収めています。また、同様の画像コンポーネントで他の技術スタックとの連携も行っています。
Leboncoin は、以前の JavaScript フロントエンドを Next.js に移行した際に、Next.js Image コンポーネントを使用するように画像パイプラインをアップグレードしました。<img>
から next/image に移行したページでは、LCP が 2.4 秒から 1.7 秒に低下しました。ページに対してダウンロードされた画像の合計バイト数は 663 KB から 326 KB になりました(遅延読み込みされる画像バイトは約 100 KB)。
得られた教訓
Next.js アプリを作成するすべてのユーザーは、Next.js Image コンポーネントを使用して最適化を行うことができます。しかし、別のフレームワークや CMS で同様のパフォーマンスの抽象化を構築したい場合、その過程で役に立ちそうな教訓をいくつか紹介します。
安全弁は、メリットよりもデメリットが大きい場合があります
Next.js Image コンポーネントの初期リリースでは、デベロッパーがサイズ要件を回避し、指定されていないサイズの画像を使用できるようにする unsized
属性が用意されていました。これは、画像の高さや幅を事前に把握できない場合に必要と判断されました。しかし、サイズ要件に関する問題の万能な解決策として、GitHub の問題で unsized
属性が推奨されていることがわかりました。CLS を悪化させずに問題を解決できる場合でも、unsized
属性が推奨されています。その後、unsized
属性を非推奨にして削除しました。
有用な摩擦と無意味な煩わしさを区別する
画像のサイズ変更の要件は、「有用な摩擦」の例です。コンポーネントの使用は制限されますが、その代わりにパフォーマンスが大幅に向上します。潜在的なパフォーマンス上のメリットを明確に把握できれば、ユーザーは制約を受け入れやすくなります。したがって、コンポーネントに関するドキュメントやその他の公開資料で、このトレードオフについて説明することをおすすめします。
ただし、パフォーマンスを犠牲にすることなく、このような不便さを回避する方法はあります。たとえば、Next.js Image コンポーネントの開発中に、ローカルに保存されている画像のサイズを調べるのが面倒だという苦情が寄せられました。静的画像インポートを追加しました。これにより、Babel プラグインを使用してビルド時にローカル画像のディメンションを自動的に取得することで、このプロセスを効率化できます。
便利な機能とパフォーマンスの最適化のバランスを取る
画像コンポーネントがユーザーに「有用な負担」を強いるだけの場合、デベロッパーはこれを使いたがらない傾向があります。画像のサイズ調整や srcset
値の自動生成などのパフォーマンス機能が最も重要であることが判明しました。自動遅延読み込みやぼやけたプレースホルダの組み込みなど、デベロッパー向けの便利な機能も Next.js Image コンポーネントに関心を寄せています。
機能のロードマップを設定して、導入を促進する
あらゆる状況で完璧に機能するソリューションを構築することは非常に困難です。75% の人にとってうまく機能するものを設計し、残りの 25% に「この場合、このコンポーネントは自分には関係ない」と伝えたくなるかもしれません。
実際には、この戦略はコンポーネント デザイナーとしての目標と相反することになります。パフォーマンス上のメリットを享受するために、開発者にコンポーネントを採用してもらいたいと考えています。移行できず、会話から取り残されたと感じているユーザーがいると、これは困難です。失望を表明し、ネガティブな認識につながり、採用に影響する可能性があります。
長期にわたってすべての合理的なユースケースをカバーするコンポーネントのロードマップを用意することをおすすめします。また、コンポーネントが解決する問題について期待値を設定するために、サポートされていない内容とその理由をドキュメントで明記することも役立ちます。
まとめ
画像の使用と最適化は複雑です。デベロッパーは、優れたユーザー エクスペリエンスを確保しながら、画像のパフォーマンスと品質のバランスを取る必要があります。このため、画像の最適化はコストと影響の大きい作業となります。
各アプリが毎回自力で開発するのではなく、デベロッパー、フレームワーク、その他のテクノロジー スタックが独自の実装のリファレンスとして使用できるベスト プラクティス テンプレートを作成しました。この経験は、他のフレームワークの画像コンポーネントをサポートする際に役立つでしょう。
Next.js Image コンポーネントにより、Next.js アプリケーションのパフォーマンスが改善され、ユーザー エクスペリエンスが向上しました。これは、幅広いエコシステムでうまく機能する優れたモデルであると考えています。このモデルをプロジェクトに採用したいとお考えのデベロッパーの方からのご意見をお待ちしております。