Next.js の Image コンポーネントの使い方をまとめてみた

2023.07.07

西田@CX事業本部です

今回は、Next.js の Image Component についてよく使われる機能を中心にまとめてみました。なおこの記事は、執筆時点の最新バージョン (13.4.9) の内容に基づいて書かれています

Image Component とは?

Next.js が提供してるコンポーネントです。HTML の <img> 要素に現代のWEBのニーズに合わせて機能を拡張し、最適な形で画像を配信させることができるコンポーネントです

主に以下の機能を備えています

  • デバイスに合わせて、画像のサイズやフォーマット(avif, webp…)を最適化し、画像のファイルサイズを削減し、画像のロード時間を短くします
  • 画像の表示領域を画像がダウンロードされる前から確保し Cumulative Layout Shift (CLS) を防止します

画像のサイズ

画像の読み込み時に発生してしまう Cumulative Layout Shift (CLS) を防ぐために、あらかじめWidth と Height を指定し画像が表示される領域を確保しておく必要があります。Next.js では画像の表示サイズを、その画像の取得先に合わせた方法で取得します

Next.js では大きく分けて以下の2種類の画像の取得先があります

  1. ローカル画像
  2. リモート画像

ローカル画像はアプリケーションのリポジトリに含まれる画像です。主に public フォルダ配下に配置され配信されます。

リモート画像はアプリケーション実行時に取得する画像です。主に CMS やS3 などから配信され、アプリケーションのリポジトリに含まれない画像です

ローカル画像のサイズ指定

ローカル画像は画像ファイルを import して Image コンポーネントの src プロパティに指定することで表示できます

import Image from "next/image";
import localImage from "../../public/cat_500x500.jpeg";

export default function Home() {
  return (
    <main className="flex justify-center items-center min-h-screen">
      <Image src={localImage} alt="猫は最高に可愛い" />
    </main>
  );
}

以下は実際に出力された <img> タグです。Next.js はビルド時に画像ファイルから、そのサイズ(500x500)を読み取り、 <img> タグの widthheight 属性に画像のサイズを指定します

src プロパティには、 /_next/image から始まるパスが指定されてますが、最適な画像を配信するための仕組みになります。こちらについては後ほど説明させていただきます

<img width="500" height="500" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fcat_500x500.3166d87a.jpeg&w=1080&q=75" ...省略 />

リモート画像のサイズ指定

リモート画像はsrc属性に画像のパスを文字列で指定することで表示できます。ビルド時に画像のサイズを取得することができないため、Image コンポーネントに width と height プロパティに画像のサイズを明示的に指定する必要があります

import Image from "next/image";

export default function Home() {
  return (
    <main className="flex justify-center items-center min-h-screen">
      <Image
        src="/cat_500x500.jpeg"
        height={500}
        width={500}
        alt="猫は最高に可愛い"
      />
    </main>
  );
}

画像を表示させるサイズが可変の場合の実装例

画像を表示させるサイズが可変の場合があります。例えば以下のようなケースです

  • レスポンシブ
  • コンテナ
  • 背景画像(バックグラウンドイメージ)

これらのケースの具体的な実装方法について、公式のサンプルを参考に見ていきたいと思います(この記事では公式のサンプルをもとに Tailwind で実装しています)

レスポンシブ

以下の例は表示サイズが 768px を境に、1カラムから2カラムに切り替わるレスポンシブの例です。表示幅が大きく2カラムで表示される場合は、画像は表示幅の50%のサイズになります。表示幅が狭くなり1カラムになった場合は、画像は表示幅の100%のサイズになります

import Image from "next/image";
import cat2048 from "../../public/cat_2048x2048.jpeg";

export default function Responsive() {
  return (
    <main className="flex justify-center items-center">
      <div className="flex flex-col min-h-screen md:flex-row">
        <Image
          src={cat2048}
          alt="猫は最高に可愛い"
          sizes="(max-width: 768px) 100vw, 50vw"
          className="flex-1 w-full h-auto object-cover"
        />
        <div className="flex-1 p-4">
          <h2 className="text-lg font-bold">猫は最高に可愛い</h2>
          <section>
            <p>
              猫は可愛い。異論は認めません。異論は認めません異論は認めません異論は認めません
            </p>
          </section>
        </div>
      </div>
    </main>
  );
}

レスポンシブで表示する画像の大きさが変わる場合は Imageコンポーネントの sizes プロパティにそれぞれの表示幅に合わせた画像サイズの値を指定することで、ダウンロードする画像のファイルサイズを削減することができます。 sizes プロパティはそのまま <img> タグの sizes 属性に渡されます

<img sizes="(max-width: 768px) 100vw, 50vw" ...省略 />

今回の例では768px以下の場合は表示領域の幅いっぱいに画像が表示されるので 100vwを指定しています。それ以上の幅の場合は表示領域の幅の50%の幅に画像を表示するので50vwを指定しています。この指定をすることで画面の幅に合わせたサイズの画像がダウンロードされます。sizesに指定した値に対応した、srcsetの指定も合わせて必要ですが、これは Next.js が適切な画像を生成し、そのパスを自動で <img> タグの srcset 属性に設定してくれます。この仕組みについては後ほど補足します

サイズが可変グリッドレイアウト

以下の例は要素の数や幅が可変でその中に画像がいっぱいに広がるようなレイアウトです

export default function Fill() {
  return (
    <main>
      <div className="grid grid-cols-[repeat(auto-fit,minmax(400px,auto))] gap-2 ">
        {[...Array(5)].map((_, index) => {
          return (
            <div key={index} className="h-[400px] relative">
              <Image
                src={cat2048}
                alt="猫は最高に可愛い"
                className="object-cover"
                fill
              />
            </div>
          );
        })}
      </div>
    </main>
  );
}

このようなレイアウトには Image コンポーネントの fill プロパティが使えます。 fill プロパティを指定すると、Image コンポーネントの親要素、今回の例で言うと、Image コンポーネントの直上の <div> 要素いっぱいに画像が広がります。今回の例の <div> 要素のサイズは高さを400pxで固定し、横幅が grid によって画面幅に合わせて可変になるように設定してます。画像はこのdiv要素の幅に合わせて伸縮します

div要素に position: relative を指定していますが、これは Image コンポーネントにfillプロパティを設定すると出力されるimg要素に position: absolute が設定されるため、必ず指定しておく必要があります

背景画像(Background Image)

背景画像も Image コンポーネントの機能を利用したい場合は、Imageコンポーネントを使って背景画像を表示する必要があります

export default function Background() {
  return (
    <main>
      <div className="fixed h-screen w-screen -z-50">
        <Image
          src={cat2048}
          alt="猫は最高に可愛い"
          className="object-cover"
          quality={100}
          sizes="100vw"
          fill
        />
      </div>
      <div className="p-4">
        <h2>猫しか勝たん</h2>
        <section>猫は最強。</section>
      </div>
    </main>
  );
}

この場合も Image コンポーネントの fill プロパティを使います。viewport全体に広がる <div> 要素を配置し、その直下に Image コンポーネントを配置します。スクロールにも対応するため div要素には position: fixed を指定しています。背景画像を最も後ろの重なり順とするために、 -(マイナス)値の z-index プロパティも合わせて指定してます

画像の最適化

Next.js は配信する画像に以下の最適化を行なっています。

  • 最適なサイズの画像を生成
  • ブラウザの対応状況に合わせてフォーマットの変換

ローカル画像の最適化

生成された <img> 要素の src属性に設定された文字列を見てみます

/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fcat_500x500.3166d87a.jpeg&w=1080&q=75

これをURLデコードすると以下のようになります

/_next/image?url=/_next/static/media/cat_500x500.3166d87a.jpeg&w=1080&q=75

ポイントを説明すると

  • /cat_500x500.jpeg へのリクエストが/_next/image/ に対してのリクエストに変換されている
  • queryパラメーターに画像最適化のためのパラメーターが追加されています&w=1080&q=75。wパラメーターは要求する画像の横幅を示し、qパラメーターは画像のクオリティを指定しています。qは指定しなければ常に 75(%) が指定されます

この最適化のためのパラメーターをURLに付与する仕組みを image-loader といいます

Image コンポーネントの loader プロパティでカスタマイズした image-loader を設定できますが、何も指定しなければデフォルトの image-loader が使われます

デフォルトの image-loader は sizes で指定された値をもとに必要な画像の幅を計算し、wパラメーターに設定し、必要な画像のクオリティをqパラメーターに設定します

クライアントから画像へのリクエストがあった時に、Next.js はこれらのパラメーターをもとに画像のサイズをリサイズし、クライアントから送られてきたHTTPの Acceptヘッダの値 を元に最適な画像のフォーマットに変換します。この変換には sharp が使われています

画像ファイルは .next/cache/images/mediaディレクトリ配下に作成され。リクエストされたクライアントに保存された画像をレスポンスとして返します。生成された画像はそのまましばらくの間残り、同一のリクエストに対しては同じファイルを返すようになります

リモート画像の最適化

ローカル画像は Next.js が最適化を行ないますが、リモート画像についてはその画像を配信しているCDN等が最適化を行います(対応していれば)。Imageコンポーネントには使用しているCDNに合わせた loader を作成し、loader プロパティに渡すことで、画像の最適化に必要な情報を画像の配信もとに渡すことができます

loader とは src, width, quality と3つのパラメーターを受け付けてURLを生成する関数です

const imageLoader = ({ src, width, quality }) => {
  return `https://example.com/${src}?w=${width}&q=${quality || 75}`
}

これを Image コンポーネントの loader プロパティに渡します

<Image
  loader={imageLoader}
  src="me.png"
  alt="Picture of the author"
  width={500}
  height={500}
/>

画像の配信にCDNを使ってる場合は、Next.js が各 CDN 毎の loader の Examples が公開しています。

以下は Imgix を使った時の loader の例になります

export default function imgixLoader({ src, width, quality }) {
  const url = new URL(`https://example.com${src}`)
  const params = url.searchParams
  params.set('auto', params.getAll('auto').join(',') || 'format')
  params.set('fit', params.get('fit') || 'max')
  params.set('w', params.get('w') || width.toString())
  params.set('q', quality.toString() || '50')
  return url.href
}

画像のキャッシュについて

Imageコンポーネントは、画像のキャッシュの設定も行います

ローカル画像のキャッシュ

ローカル画像を import して Image コンポーネントの src プロパティに渡してる場合は、Next.js はビルド時に画像のハッシュ値を計算し、画像のURLにそのハッシュ値を含めます。そして、半永久的にキャッシュする設定の Cache-Controle ヘッダをつけて画像を配信します

import Image from "next/image";
import localImage from "../../public/cat_500x500.jpeg";

export default function Home() {
  return (
    <main className="flex justify-center items-center min-h-screen">
      <Image src={localImage} alt="猫は最高に可愛い" />
    </main>
  );
}

配信されるファイルパス

/_next/image?url=/_next/static/media/cat_500x500.3166d87a.jpeg

Cache-Controlヘッダ

Cache-Control: public, max-age=315360000, immutable

半永久的にキャッシュする設定ですが、画像のパスに画像ファイルのハッシュ値が含まれるため、画像を差し替えた場合は、別のURLとなり、キャッシュされてしまっていたとしても、別の画像としてダウンロードすることができます

リモート画像のキャッシュ

リモート画像の場合、 Next.js は画像ファイルのハッシュ値を、ビルド時に計算することができません

import Image from "next/image";

export default function Home() {
  return (
    <main className="flex justify-center items-center min-h-screen">
      <Image
        src="/cat_500x500.jpeg"
        height={500}
        width={500}
        alt="猫は最高に可愛い"
      />
    </main>
  );
}

next.config.js に設定された minimumCacheTTL の値かリモート画像ファイルの Cache-Control ヘッダの値の大きい方をもとに Cache-Control ヘッダにキャッシュの設定を行います。

module.exports = {
  images: {
    minimumCacheTTL: 60,
  },
}
Cache-Control: public, max-age=60, must-revalidate

現時点では、CDN、Proxy、ブラウザ等に保存されたキャッシュを明示的に全て削除する手段が用意されていないため、キャッシュされる時間を短く設定することが推奨されています

画像のプリロード

Largest Contentful Paint (LCP) を防ぐためにヒーロー画像などをプリロードしたいケースがあると思います。このようなケースの場合は priority プロパティに true を設定することで対応できます

priority を有効にすることで出力されるimg要素の fetchpriority 属性に “high” が設定され、画像がダウンロードされる優先度が高くなります。また、Imageコンポーネントを使うとデフォルトで loading 属性に lazy が設定され必要な時までダウンロードを遅らせていますが、その指定がなくなります

<Image
  src={cat2048}
  alt="猫は最高に可愛い"
  priority={true}
  fill
/>

画像ダウンロード中のプレースフォルダ表示

Imageコンポーネントの placeholder プロパティに “blur” を指定すると、自動で、 blur 画像を生成し、画像のダウンロード中に表示してくれます

<Image
  src={cat2048}
  alt="猫は最高に可愛い"
  placeholder="blur"
  fill
/>

参考

https://nextjs.org/docs/pages/api-reference/components/image

https://nextjs.org/docs/pages/building-your-application/optimizing/images

https://web.dev/i18n/ja/cls/

https://nextjs.org/learn/seo/web-performance/lcp

https://placekitten.com/