効果的な画像コンポーネントの作成

画像コンポーネントは、パフォーマンスのベスト プラクティスをカプセル化し、画像を最適化するためのすぐに使えるソリューションを提供します。

Leena Sohoni
Leena Sohoni
Kara Erickson
Kara Erickson
Alex Castle
Alex Castle

画像はウェブ アプリケーションのパフォーマンス ボトルネックの一般的な原因であり、最適化の重要な焦点です。最適化されていない画像はページの肥大化の原因となり、90thでは、ページの重さ(バイト単位)の 70% 以上を占めています。画像を最適化する方法は複数あるため、パフォーマンス ソリューションがデフォルトで組み込まれたインテリジェントな「画像コンポーネント」が必要です。

Aurora チームは Next.js と連携して、このようなコンポーネントを構築しました。目標は、ウェブ デベロッパーがさらにカスタマイズできる最適化されたイメージ テンプレートを作成することでした。このコンポーネントは、他のフレームワーク、コンテンツ管理システム(CMS)、技術スタックで画像コンポーネントを構築するための優れたモデルと標準になります。同様のNuxt.js 用コンポーネントで共同作業を行っており、今後のバージョンでの画像最適化について Angular と連携しています。この記事では、Next.js Image コンポーネントの設計方法と、その過程で得られた教訓について説明します。

画像コンポーネントを画像の拡張機能として使用する

画像の最適化に関する問題と機会

画像はパフォーマンスだけでなく、ビジネスにも影響します。ページ上の画像の数は、ウェブサイトにアクセスするユーザーのコンバージョンを予測するうえで 2 番目に重要な要素でした。ユーザーがコンバージョンに至ったセッションでは、コンバージョンに至らなかったセッションよりも画像が 38% 少ないことがわかりました。Lighthouse は、ベスト プラクティス監査の一環として、画像を最適化して ウェブバイタルを改善するための複数の機会をリストします。画像がウェブに関する主な指標とユーザー エクスペリエンスに影響を与える一般的な領域は次のとおりです。

サイズ指定されていない画像は CLS に悪影響を及ぼす

サイズが指定されていない画像が配信されると、レイアウトが不安定になり、累積レイアウト シフト(CLS)が高くなる可能性があります。<img> 要素に width 属性と height 属性を設定すると、レイアウト シフトを防ぐことができます。次に例を示します。

<img src="flower.jpg" width="360" height="240">

レンダリングされた画像のアスペクト比が本来のアスペクト比に近づくように、幅と高さを設定する必要があります。アスペクト比が大きく異なると、画像が歪んで見えることがあります。CSS でアスペクト比を指定できる比較的新しいプロパティを使用すると、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 では Fetch Priority 機能が オリジン トライアルとして導入されています。この属性は、ページ上の特定の画像の優先度を高めるために使用できます。全体として、このような機能強化がコンポーネント レベルで評価され、実装されると、デベロッパーはより簡単に作業できるようになります。

ソリューションとしての画像コンポーネント

画像を最適化する機会と、すべてのアプリケーションで個別に実装する際の課題から、画像コンポーネントのアイデアが生まれました。イメージ コンポーネントは、ベスト プラクティスをカプセル化して適用できます。<img> 要素を画像コンポーネントに置き換えることで、デベロッパーは画像のパフォーマンスに関する問題をより適切に対処できます。

昨年、Google は Next.js フレームワークと協力して、Image コンポーネントを設計し、実装しました。次のように、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 コンポーネントのすべてのインスタンスに適用されます。そこで、次の 3 つの要素からなるソリューションを考案しました。

  1. deviceSizes プロパティ: このプロパティを使用すると、アプリのユーザーベースに共通するデバイスに基づいてブレークポイントを 1 回だけ構成できます。ブレークポイントのデフォルト値は構成ファイルに含まれています。
  2. imageSizes プロパティ: デバイスサイズのブレークポイントに対応する画像サイズを取得するために使用される、構成可能なプロパティです。
  3. 各画像の layout 属性: 各画像で deviceSizes プロパティと imageSizes プロパティをどのように使用するかを指定するために使用されます。レイアウト モードでサポートされている値は、fixed、fill、intrinsic、responsive です。

レイアウト モードが responsive または fill の画像がリクエストされると、Next.js はページをリクエストしているデバイスのサイズに基づいて提供する画像を特定し、画像の srcset と sizes を適切に設定します。

次の比較は、レイアウト モードを使用してさまざまな画面で画像のサイズを制御する方法を示しています。Next.js ドキュメントで共有されているデモ画像を、スマートフォンと標準的なノートパソコンで表示しました。

ノートパソコンの画面 電話による選抜
レイアウト = 固有: 小さいビューポートでコンテナの幅に合わせて縮小します。大きなビューポートで、画像の固有サイズを超えてスケールアップされない。コンテナの幅が 100% に設定されている
山の画像がそのまま表示されている 山岳の画像を縮小
レイアウト = 固定: 画像がレスポンシブではありません。幅と高さは、レンダリングされるデバイスに関係なく、`` 要素と同様に固定されます。
山の画像がそのまま表示されている 山脈の画像がそのまま表示され、画面に収まらない
レイアウト = レスポンシブ: さまざまなビューポートのコンテナの幅に応じてスケールダウンまたはスケールアップし、アスペクト比を維持します。
画面に合わせて拡大された山の画像 画面に合わせて縮小された山の画像
レイアウト = 塗りつぶし: 幅と高さが親コンテナいっぱいに拡大されます。(この例では、親の <div> の幅は 300*500 に設定されています)。
300×500 サイズに合うようにレンダリングされた山の画像 300×500 サイズに合うようにレンダリングされた山の画像
さまざまなレイアウトでレンダリングされた画像

組み込みの遅延読み込みを提供する

Image コンポーネントは、デフォルトで組み込みのパフォーマンスの高い遅延読み込みソリューションを提供します。<img> 要素を使用する場合、遅延読み込みにはいくつかのオプションがありますが、どれも使用が難しい欠点があります。デベロッパーは、次のいずれかの遅延読み込みアプローチを採用できます。

  • loading 属性を指定します。これはすべての最新ブラウザでサポートされています。
  • Intersection Observer API を使用する: カスタムの遅延読み込みソリューションを構築するには、労力と慎重な設計と実装が必要です。デベロッパーが常にこのための時間を確保できるとは限りません。
  • 画像を遅延読み込みするためのサードパーティ ライブラリをインポートする: 遅延読み込みに適したサードパーティ ライブラリを評価して統合するには、追加の作業が必要になる場合があります。

Next.js Image コンポーネントでは、読み込みはデフォルトで "lazy" に設定されています。遅延読み込みは Intersection Observer を使用して実装されており、最新のブラウザのほとんどで利用できます。有効にするためにデベロッパーが特別な操作を行う必要はありませんが、必要に応じて無効にすることはできます。

重要な画像をプリロードする

LCP 要素は画像であることが多く、大きな画像は LCP を遅らせる可能性があります。ブラウザが画像をより早く検出できるように、重要な画像をプリロードすることをおすすめします。<img> 要素を使用する場合は、次のように HTML の head にプリロード ヒントを含めることができます。

<link rel="preload" as="image" href="important.png">

適切に設計された画像コンポーネントは、使用するフレームワークに関係なく、画像の読み込み順序を調整する方法を提供する必要があります。Next.js Image コンポーネントの場合、デベロッパーは画像コンポーネントの 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 がインストールされている場合、本番環境の画像変換に Sharp を使用します。このライブラリは、独自の画像最適化パイプラインを構築しようとしているユーザーに最適です。

プログレッシブ ローディングをサポート

プログレッシブ ローディングは、実際の画像が読み込まれる間、通常は画質が大幅に低いプレースホルダ画像を表示して、ユーザーの関心を維持するために使用される手法です。認識されるパフォーマンスが向上し、ユーザー エクスペリエンスが向上します。ファーストビューの下の画像またはファーストビューの上の画像で遅延読み込みと組み合わせて使用できます。

Next.js Image コンポーネントは、placeholder プロパティを使用して、画像のプログレッシブ読み込みをサポートしています。これは、実際の画像の読み込み中に低画質またはぼかしの入った画像を表示するための 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 属性を提供しました。これは、画像の高さや幅を事前に把握できない場合に必要になると考えられます。しかし、CLS を悪化させずに問題を解決できる場合でも、GitHub の問題で unsized 属性をサイズ要件に関する問題の万能な解決策として推奨しているユーザーがいることがわかりました。その後、unsized 属性は非推奨となり、削除されました。

有用な摩擦と無意味な煩わしさを区別する

画像のサイズ設定の要件は、「有用な摩擦」の例です。コンポーネントの使用は制限されますが、代わりに大きなパフォーマンス上のメリットが得られます。ユーザーは、パフォーマンスのメリットを明確に把握していれば、制約を容易に受け入れます。そのため、コンポーネントに関するドキュメントやその他の公開資料で、このトレードオフについて説明する価値があります。

ただし、パフォーマンスを犠牲にすることなく、こうした摩擦を回避する方法を見つけることができます。たとえば、Next.js Image コンポーネントの開発中に、ローカルに保存された画像のサイズを調べるのが面倒だという苦情が寄せられました。静的画像インポートを追加しました。これにより、Babel プラグインを使用してビルド時にローカル画像のディメンションを自動的に取得することで、このプロセスを効率化します。

利便性機能とパフォーマンスの最適化のバランスを取る

画像コンポーネントがユーザーに「有用な摩擦」を課すだけの場合、デベロッパーはそれを使用したがらない傾向があります。画像サイズ設定や srcset 値の自動生成などのパフォーマンス機能が最も重要であることがわかりました。自動遅延読み込みや組み込みのぼかしプレースホルダなどのデベロッパー向けの便利な機能も、Next.js Image コンポーネントへの関心を高めました。

導入を促進する機能のロードマップを設定する

あらゆる状況で完璧に機能するソリューションを構築することは非常に困難です。75% のユーザーにとって使いやすいものを設計し、残りの 25% のユーザーには「このコンポーネントは、このような場合には適していません」と伝えるのは簡単です。

実際には、この戦略はコンポーネント デザイナーとしての目標と矛盾することがあります。パフォーマンスのメリットを享受するために、デベロッパーにコンポーネントを採用してもらいたい。移行できないユーザーがいて、会話から取り残されたと感じている場合は、この対応は困難です。失望を表明する可能性が高く、導入に影響する否定的な認識につながります。

コンポーネントのロードマップを作成し、長期にわたって妥当なユースケースをすべてカバーすることをおすすめします。また、コンポーネントが解決することを目的とする問題について期待値を設定するために、サポートされていないこととその理由をドキュメントで明示することも重要です。

まとめ

画像の利用と最適化が複雑です。デベロッパーは、優れたユーザー エクスペリエンスを確保しながら、パフォーマンスと画質のバランスを見つける必要があります。そのため、画像最適化はコストが高く、影響力の大きい取り組みとなります。

各アプリが毎回車輪の再発明を行うのではなく、デベロッパー、フレームワーク、その他の技術スタックが独自の実装の参考として使用できるベスト プラクティス テンプレートを作成しました。この経験は、イメージ コンポーネントで他のフレームワークをサポートするうえで、間違いなく貴重なものとなるでしょう。

Next.js Image コンポーネントは、Next.js アプリケーションのパフォーマンスを向上させ、ユーザー エクスペリエンスを高めることに成功しています。これは、より広範なエコシステムでうまく機能する優れたモデルであると考えています。プロジェクトでこのモデルを採用したいとお考えのデベロッパーの皆様からのご意見をお待ちしております。