Next.js でアプリケーションを構築している人の中には画像が自動で最適化されると聞いているので Image コンポーネントを利用しているけど実際にどのような設定が行われているかあまりよくわかっていないという人もいるのではないでしょうか。本文書では Next.js の Image コンポーネントを利用することでどのような設定が行われるのか実際に Next.js を動かしながら確認しています。フロントエンドの最先端の JavaScript フレームワークの一つである Next.js の画像の最適化を理解することで現在画像の最適化のためにどのようなアプローチが行われているかということも理解することができます。

Image コンポーネントに限りませんが WEB サイトでの画像の最適化のポイントには 3 つあります。

  1. アクセスしてくるデバイスによって適切なサイズの画像をダウンロードさせること
  2. 適切なタイミングで画像をダウンロードさせること
  3. 画質を落とさず可能な限り容量の小さな画像をダウンロードさせること

Next.js プロジェクトの作成

npx create-next-app@latest コマンドを利用してプロジェクトの作成を行います。npx create-next-app@latest コマンドを実行すると TypeScript, ESLint などをプロジェクトで利用するかどうか聞かれますがすべてデフォルトの値を選択しています。プロジェクト名は任意の名前をつけることができるのでここでは nextjs-image という名前をつけています。


 % npx create-next-app@latest
✔ What is your project named? … nextjs-image
✔ Would you like to use TypeScript? … No / Yes
✔ Would you like to use ESLint? … No / Yes
✔ Would you like to use Tailwind CSS? … No / Yes
✔ Would you like to use `src/` directory? … No / Yes
✔ Would you like to use App Router? (recommended) … No / Yes
✔ Would you like to customize the default import alias? … No / Yes
Creating a new Next.js app in /Users/mac/Desktop/nextjs-image.

Using npm.

Initializing project with template: app-tw


Installing dependencies:
- react
- react-dom
- next

Installing devDependencies:
- typescript
- @types/react
- @types/node
- @types/react-dom
- autoprefixer
- postcss
- tailwindcss
- eslint
- eslint-config-next


added 331 packages, and audited 332 packages in 23s

117 packages are looking for funding
  run `npm fund` for details

2 low severity vulnerabilities

To address all issues (including breaking changes), run:
  npm audit fix --force

Run `npm audit` for details.
Initialized a git repository.

Success! Created nextjs-image at /Users/mac/Desktop/nextjs-image

インストール後の package.json の中身は下記の通りです。バージョンが latest と表示されていますが動作確認した時の Next.js の最新バージョンは 13.5.3 でした。


{
  "name": "nextjs-image",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "react": "latest",
    "react-dom": "latest",
    "next": "latest"
  },
  "devDependencies": {
    "typescript": "latest",
    "@types/react": "latest",
    "@types/node": "latest",
    "@types/react-dom": "latest",
    "autoprefixer": "latest",
    "postcss": "latest",
    "tailwindcss": "latest",
    "eslint": "latest",
    "eslint-config-next": "latest"
  }
}

Next.js での画像設定

Image コンポーネントに限定せず Next.js での画像の表示方法について確認を行っていきます。

img タグを利用した場合

最初に img タグを利用した場合の画像の表示方法を確認します。

app ディレクトリの中に components ディレクトリを作成して NextImage.tsx ファイルを作成します。本文書では鳥の画像 bird.jpg を利用します。用意した画像ファイル bird.jpg ファイルを public ディレクトリに保存します。public の直下に保存した画像は/bird.jpg で指定することができます。


export function NextImage() {
  return <img src="/bird.jpg" />;
}

作成した NextImage.tsx ファイルを app ディレクトリにある page.tsx から import します。page.tsx ファイルに以下のコードを記述します。


import { NextImage } from './components/NextImage';

export default function Home() {
  return (
    <main className="p-8">
      <h1>Imageコンポーネントの動作確認</h1>
      <NextImage />
    </main>
  );
}

デフォルトで page.tsx ファイルに global.css のスタイルが設定されているので app ディレクトリの globals.css ファイルのスタイルを削除しておきます。Tailwind CSS のディレクティブも削除しています。

“npm run dev”コマンドを実行するとブラウザ上に画像が表示されます。

birdの画像
birdの画像

ブラウザのデベロッパーツールで確認すると以下のように表示されます。


<img src="/bird.jpg" />

Image コンポーネントを利用

img タグで利用した public フォルダの bird.jpg ファイルを Image コンポーネントを利用して表示します。


import Image from 'next/image';

export function NextImage() {
    return <Image src<="/bird.jpg" />;
}

ブラウザ上には”Unhandled Runtime Error”が表示され width 属性が必要であることがわかります。


Unhandled Runtime Error Error: Image with src "/bird.jpg" is missing required
"width" property.

画像のプロパティから高さの情報を確認して width を設定します。


<Image src="/bird.jpg" width={1280} />

width 属性を設定してもエラーが発生します。エラーから height 属性も必要であることがわかります。


Unhandled Runtime Error Error: Image with src "/bird.jpg" is missing required
"height" property.

height 属性の設定を行います。


<Image src="/bird.jpg" width={1280} height={852} />

画面上には bird.jpg の画像が表示されますがブラウザのデベロッパーツールを確認するとエラーが表示されています。


app-index.js:32 Image is missing required "alt" property. Please add Alternative
Text to describe the image for screen readers and search engines.

エラーの内容から alt 属性で画像の説明を設定する必要があることがわかります。


<Image src="/bird.jpg" width={1280} height={852} alt="a bird" />;

ブラウザのデベロッパーツールからエラーは消えましたが Warning で以下のメッセージが表示されています。


react_devtools_backend_compact.js:2367 Image with src "/bird.jpg" was detected
as the Largest Contentful Paint (LCP). Please add the "priority" property if
this image is above the fold.

画像の bird.jpg が LCP(Largest Contentfull Paint)として認識されているので priority を設定してくださいと表示されているので設定を行います。

“above the fold”はスクロールせずに閲覧可能なサイトのファースト ビューを指します。
fukidashi

<Image src="/bird.jpg" width={1280} height={852} alt="a bird" priority />;

priority の設定により Warning は解消されます。

priority に値を設定していませんが設定していない場合は true が設定されます。priority={true}と記述することもできます。
fukidashi

Image コンポーネントで public に保存した画像を利用する場合は img タグとは異なり、width, height, alt を設定が必要であることがわかります。さらに画像が LCP として認識された場合には priority の設定を行う必要があります。

priority の設定

メッセージに表示されている通りに priority の設定を行いましたが priority によってどのような設定が行われるのか確認します。

設定を行った場合と行っていない場合の比較を行いたいので先に priority をつけていない場合の要素を確認します。img タグとは異なり Image コンポーネントを利用することでさまざまな属性が追加されています。


<img
  alt="a bird"
  width="1280"
  height="852"
  decoding="async"
  data-nimg="1"
  style="color:transparent"
  srcset="/_next/image?url=%2Fbird.jpg&w=1920&q=75 1x,
  /_next/image?url=%2Fbird.jpg&w=3840&q=75 2x"
  src="/_next/image?url=%2Fbird.jpg&w=3840&q=75"
  loading="lazy"
>

次に priority を設定した場合の要素です。


<img
  alt="a bird"
  fetchpriority="high"
  width="1280"
  height="852"
  decoding="async"
  data-nimg="1"
  style="color:transparent"
  srcset="/_next/image?url=%2Fbird.jpg&w=1920&q=75 1x,
   /_next/image?url=%2Fbird.jpg&w=3840&q=75 2x"
  src="/_next/image?url=%2Fbird.jpg&w=3840&q=75"
>

2 つの img タグの属性を比較すると proority を設定する前は loading 属性に lazy が設定されていますが loading 属性がなくなり代わりに fetchpriority 属性 で high が設定されています。

loading 属性の lazy は画像の遅延読み込みを設定する設定でビューボート(画面)の外に画像が設置されている場合は画像の読み込みを行いませんがユーザがスクロールを行いビューポートに近づいてくると画像の読み込みが行われます。fetchpriority 属性を high に設定することで他の画像よりも優先的に画像の読み込みが行われます。つまり priority を設定することで画像の遅延読み込みではなく設定した画像をできるだけ早く読み込むようにしていることがわかります。


loading 属性の設定には”lazy”の他に”eager”があります。eager を設定した場合は遅延読み込みが行われるずページを開くとビューポートに関係なく画像の読み込みが行われます。
fukidashi

LCP(Largest Contentfull Paint)はビューポート内で最もサイズが大きいコンテンツ要素の読み込みを計測するものなので priority を設定することで LCP に対する対策を行っていることがわかります。

ページを開いた時のビューポートに画像が含まれていない場合は”Please add the “priority” property if this image is above the fold.”の Warning が表示されることはありません。
fukidashi

fetchpriority、loading 以外にも srcset や decoding などが設定されています。srcset については後ほど説明を行います。decoding に async(非同期)が設定されているのは画像のデコードを非同期で行わせるためです。デフォルトでは sync(同期)が設定されており、画像のデコード処理が同期的に行われるため画像以外のページの表示の処理が遅延する可能性があります。decoding の値を async に設定することで遅延が発生しないようにしています。

ここまでの確認で Image コンポーネントは画像の最適化のため loading, decoding, fetchpriority の設定を行っていることがわかります。これらの属性は Image コンポーネントを利用しない場合でも WEB サイトの高速化を図る上で積極的に活用したい属性なのでこれまであまり意識してこなかった人はぜひこの機会に理解を深めてください。

画像を import した場合

先ほどまでは public に保存した画像をパスを利用して src 属性に設定していましたがここでは画像を import して import した画像を src 属性に設定します。



import Image from 'next/image';
import Bird from '../../../public/bird.jpg';

export function NextImage() {
  return <Image src={Bird} alt="a bird" />;
}

priority 属性は設定していません。
fukidashi

上記の設定のようにローカルの public フォルダから画像を import した場合には width, height を指定する必要はありません。

ブラウザのデベロッパーツールで要素を確認すると下記のように width と height が自動で設定されていることが確認できます。


<img
  alt="a bird"
  loading="lazy"
  width="1280"
  height="852"
  decoding="async"
  data-nimg="1"
  style="color:transparent"
  srcset="
  /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fbird.9810d6b2.jpg&amp;w=1920&amp;q=75 1x,
  /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fbird.9810d6b2.jpg&amp;w=3840&amp;q=75 2x"
  src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fbird.9810d6b2.jpg&amp;w=3840&amp;q=75"
>

画像を import した場合には width と height を省略することができましたが srcset、src 属性の違いを確認することができます。画像を import した場合には src のパラメータの url に設定されている値が下記の通りです。


src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fbird.9810d6b2.jpg&amp;w=3840&amp;q=75"

//URLをデコード
/_next/image?url=/_next/static/media/bird.9810d6b2.jpg&amp;w=3840&amp;q=75

import していない場合は下記のようになっています。


src="/_next/image?url=%2Fbird.jpg&amp;w=3840&amp;q=75"

//URLをデコード
/_next/image?url=/bird.jpg&amp;w=3840&amp;q=75

import している場合には_next フォルダの下の static/media フォルダにファイルを作成しています。import していない場合にはそのまま/public フォルダにある画像が指定されていることがわかります。

Responsive 設定

style 属性を設定することでブラウザの横幅に合わせて画像が表示されるサイズを変更することができます。


import Image from 'next/image';

export function NextImage() {
  return (
    <Image
      src="/bird.jpg"
      width={1280}
      height={852}
      alt="a bird"
      style={{ width: '100%', height: 'auto' }}
    />
  );
}

画像を import した場合も同様に設定することができます。


import Image from 'next/image';
import Bird from '../../../public/bird.jpg';

export function NextImage() {
  return (
    <Image
      src={Bird}
      alt="a bird"
      style={{ width: '100%', height: 'auto' }}
    />
  );
}

リモートサイトの画像表示

ここまでは pubic ディレクトリに保存されている bird.jpg ファイルを表示していましたがリモートサイトに保存されている画像の表示設定について確認していきます。

リモートサイトの動作確認に利用する画像はhttp://reffect.co.jp/images/bird.jpgファイルを利用します。src属性にリモートサイトのURLを指定します。publicディレクトリに保存したファイルを直接指定する場合と同様にwidth, height の設定を行います。¥


import Image from 'next/image';

export function NextImage() {
  return (
    <Image
      src="https://reffect.co.jp/images/bird.jpg"
      width={1280}
      height={852}
      alt="a bird"
      priority
    />
  );

ブラウザから確認すると以下のエラーメッセージが表示されます。


Error: Invalid src prop (https://reffect.co.jp/images/bird.jpg) on `next/image`, hostname "reffect.co.jp" is not configured under images in your `next.config.js`
See more info: https://nextjs.org/docs/messages/next-image-unconfigured-host

リモートサイトの画像を表示する場合には next.config.js にホストの設定が必要となります。


/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'reffect.co.jp',
        port: '',
        pathname: '/images/**',
      },
    ],
  },
};

module.exports = nextConfig;

要素を確認すると以下のように設定されています。


<img
  alt="a bird"
  fetchpriority="high"
  width="1280"
  height="852"
  decoding="async"
  data-nimg="1"
  style="color:transparent"
  srcset="/_next/image?url=https%3A%2F%2Freffect.co.jp%2Fimages%2Fbird.jpg&w=1920&q=75 1x,
   /_next/image?url=https%3A%2F%2Freffect.co.jp%2Fimages%2Fbird.jpg&w=3840&q=75 2x"
  src="/_next/image?url=https%3A%2F%2Freffect.co.jp%2Fimages%2Fbird.jpg&w=3840&q=75"
>

Image コンポーネントのプロパティ

ここまでの動作確認で Image コンポーネントで設定できるプロパティについては src, width, height, alt, priority の 5 つの属性(props)が出てきました。それ以外にも設定可能なプロパティを持っているのでその一部を確認しながら Image コンポーネントの理解を深めていきます。

fill の設定

fill を設定した場合にどのような変化が起こるか確認するために public フォルダに保存した bird.jpg をパスを使って src 属性に指定します。


import Image from 'next/image';

export function NextImage() {
  return <Image src="/bird.jpg" alt="a bird" fill />;
}

画像をパスで設定した場合には width, height の設定が必要でしたが fill を設定することでそれらの属性がなくても画像が表示されます。しかし画像は画面全体に広がり page.tsx で設定していた文字列の”Image コンポーネントの動作確認”も画像に隠れて見えなくなります。

画面全体に広がった画像
画面全体に広がった画像

デベロッパーツールから画像の要素を確認すると fill を設定する前よりも設定されている情報が増えていますが ここでは style 属性のみに注目します。


<img
  alt="a bird"
  loading="lazy"
  decoding="async"
  data-nimg="fill"
  style="
    position:absolute;
    height:100%;
    width:100%;
    left:0;
    top:0;
    right:0;
    bottom:0;
    color:transparent
  "
  sizes="100vw"
  srcset="
    /_next/image?url=%2Fbird.jpg&amp;w=640&amp;q=75   640w,
    /_next/image?url=%2Fbird.jpg&amp;w=750&amp;q=75   750w,
    /_next/image?url=%2Fbird.jpg&amp;w=828&amp;q=75   828w,
    /_next/image?url=%2Fbird.jpg&amp;w=1080&amp;q=75 1080w,
    /_next/image?url=%2Fbird.jpg&amp;w=1200&amp;q=75 1200w,
    /_next/image?url=%2Fbird.jpg&amp;w=1920&amp;q=75 1920w,
    /_next/image?url=%2Fbird.jpg&amp;w=2048&amp;q=75 2048w,
    /_next/image?url=%2Fbird.jpg&amp;w=3840&amp;q=75 3840w
  "
  src="/_next/image?url=%2Fbird.jpg&amp;w=3840&amp;q=75"
/>

style 属性だけ取り出すと position が absolute に設定され height と width が 100%になっています。style 属性を確認することで画面全体に広がる理由を理解することができます。


tyle="
  position:absolute;
  height:100%;
  width:100%;
  left:0;
  top:0;
  right:0;
  bottom:0;
  color:transparent
"

style 属性の設定内容から fill では position に absolute が設定されているので親要素に position を設定することで画像の制御が行えるようになります。

div 要素で Image コンポーネントを wrap して position プロパティで relative を設定します。


import Image from 'next/image';

export function NextImage() {
  return (
    <div style={{ position: 'relative' }}>
      <Image src="/bird.jpg" alt="a bird" fill />
    </div>
  );
}

”Image コンポーネントの動作確認”の文字列は表示されるようになりましたが今度は画像が表示されなくなります。ブラウザのコンソールにはメッセージが表示されます。


warn-once.js:16 Image with src "/bird.jpg" has "fill" and a height value of 0. This is likely because the parent element of the image has not been styled to have a set height.

メッセージから画像を表示させるためには height の設定が必要であることがわかります。画像をブラウザ上に表示させるために親要素に幅と高さの設定を行います。


import Image from 'next/image';

export function NextImage() {
  return (
    <div style={{ position: 'relative', width: '300px', height: '300px' }}>
      <Image src="/bird.jpg" alt="a bird" fill />
    </div>
  );
}

親要素で設定した幅 300px、高さ 300px で画像が表示されるようになります。

親要素の領域で表示
親要素の領域で表示

画像は表示されますが 300px、300px の正方形の形をした領域に横長の画像を収めようとしているので画像が横に縮んで表示されます。

height のみ設定した場合には幅がブラウザの横幅に合わせて広がります。


import Image from 'next/image';

export function NextImage() {
  return (
    <div style={{ position: 'relative', height: '300px' }}>
      <Image src="/bird.jpg" alt="a bird" fill />
    </div>
  );
}
heightのみ設定した場合
heightのみ設定した場合

Object-fit の設定

画像が 300px, 300px の枠の中に収まるように Image コンポーネントに対して object-fit の contain を設定します。


import Image from 'next/image';

export function NextImage() {
  return (
    <div style={{ position: 'relative', width: '300px', height: '300px' }}>
      <Image
        src="/bird.jpg"
        alt="a bird"
        fill
        style={{ objectFit: 'contain' }}
      />
    </div>
  );
}

object の contain を設定するとアスペクト比を保ったまま、画像が領域の中に収まるにように表示されます。横長の画像の場合は横一杯に画像が表示されます。

object-fit の contain を設定
object-fit の contain を設定
アスペクト比は画像の横と縦の比です。横が 100px で縦が 50px の場合のアスペクト比は 2:1 です。
fukidashi

親要素の背景色を入れておくと横幅いっぱいに表示されていることがわかります。


import Image from 'next/image';

export function NextImage() {
  return (
    <div
      style={{
        position: 'relative',
        width: '300px',
        height: '300px',
        backgroundColor: 'red',
      }}
    >
      <Image
        src="/bird.jpg"
        alt="a bird"
        fill
        style={{ objectFit: 'contain' }}
      />
    </div>
  );
}
親要素の背景色を設定
親要素の背景色を設定

次に object-fit プロパティの値 を cover に設定すると アスペクト 比を保持したまま親要素で指定した領域全体に表示することができます。領域全体に表示されるため contain を設定した時のように親要素に設定した背景色が表示されることはありません。


import Image from 'next/image';

export function NextImage() {
  return (
    <div
      style={{
        position: 'relative',
        width: '300px',
        height: '300px',
        backgroundColor: 'red',
      }}
    >
      <Image src="/bird.jpg" alt="a bird" fill style={{ objectFit: 'cover' }} />
    </div>
  );
}

利用している画像が横長画像なので親要素の領域に入りきらなった両側の画像の一部は切り取られて表示されます。

object-fitのcoverを設定
object-fitのcoverを設定

画像を import した場合も fill に対する設定方法は同じです。


import Image from 'next/image';
import Image from 'next/image';
import Bird from '../../../public/bird.jpg';

export function NextImage() {
  return (
    <div
      style={{
        position: 'relative',
        width: '300px',
        height: '300px',
        backgroundColor: 'red',
      }}
    >
      <Image src={Bird} alt="a bird" fill style={{ objectFit: 'cover' }} />
    </div>
  );
}

ここまでの動作確認で fill を設定した場合に style 属性の position プロパティに absolute などが設定され画面全体に表示されること。親要素を設定して object-fit プロパティを利用することでアスペクト比を保ったまま画像が表示できることを確認しました。

srcset について

本環境では fill の設定することで srcset 属性には 8 つの値が設定されています。


<img
  alt="a bird"
  loading="lazy"
  decoding="async"
  data-nimg="fill"
  style="
    position:absolute;
    height:100%;
    width:100%;
    left:0;
    top:0;
    right:0;
    bottom:0;
    color:transparent
  "
  sizes="100vw"
  srcset="
    /_next/image?url=%2Fbird.jpg&amp;w=640&amp;q=75   640w,
    /_next/image?url=%2Fbird.jpg&amp;w=750&amp;q=75   750w,
    /_next/image?url=%2Fbird.jpg&amp;w=828&amp;q=75   828w,
    /_next/image?url=%2Fbird.jpg&amp;w=1080&amp;q=75 1080w,
    /_next/image?url=%2Fbird.jpg&amp;w=1200&amp;q=75 1200w,
    /_next/image?url=%2Fbird.jpg&amp;w=1920&amp;q=75 1920w,
    /_next/image?url=%2Fbird.jpg&amp;w=2048&amp;q=75 2048w,
    /_next/image?url=%2Fbird.jpg&amp;w=3840&amp;q=75 3840w
  "
  src="/_next/image?url=%2Fbird.jpg&amp;w=3840&amp;q=75"
/>

srcset 各行には 640w, 750w といった数字が並んでいますが srcset を設定することでアクセスしてきたデバイスに合わせてダウンロードする画像を変えることができます。例えば現在使っているディスプレイのデバイスピクセル比が 1 でブラウザのウィンドウサイズの横幅が 1000px の場合は”url=%2Fbird.jpg&w=1080&q=75 1080w”で指定されている画像がダウンロードされます。デバイスピクセル比が 2 の場合は 2048w で指定されている画像をダウンロードされます。1000px のウィンドウサイズなのに 2048w の画像がダウンロードされる場合は利用しているディスプレイのデバイスピクセル比が 1 ではないので確認してみてください。

現在利用しているディスプレイのデバイスピクセル比を知りたい場合はデベロッパーツールのコンソールで devicePixelRatio と打ってください。JavaScript であれば window.devicePixelRatio で取得できます。
fukidashi

ダウンロードする画像によってどれだけ画像のサイズの違いがあるか確認すると以下のような結果になりました。bird.jpg の場合はデフォルトの画像は 131KB です。

  • 640w 18.3KB
  • 750w 23.6KB
  • 828w 27.8KB
  • 1080w 44.5KB
  • 1200w 53.6KB
  • 1920w 63.0KB
  • 2048w 63.0KB
  • 3840w 63.0KB
どのサイズをダウンロードしているかはデベロッパーツールのネットワークタブで確認できます。Next.js では一度画像を表示するとキャッシュに保存されるので動作確認する際はキャッシュの消去を忘れずに行ってください。
fukidashi

サイズによってかなり差があることがわかります。もし Image コンポーネントを利用せず img タグで何も設定せず bird.jpg を指定している場合はどのデバイスでアクセスしても ファイルサイズの 131KB をダウンロードすることになるので 影響はかなり大きいこともわかります。

fill を設定しない場合には下記のように”XXX w”ではなく”1x”,“2x”が設定さています。これはデバイスピクセス比が 1 の場合は 1x の画像をダウンロードして 2 の場合は”2x”の画像をダウンロードします。

<img
  alt="a bird"
  loading="lazy"
  width="1280"
  height="852"
  decoding="async"
  data-nimg="1"
  style="color:transparent"
  srcset="
    /_next/image?url=%2Fbird.jpg&amp;w=1920&amp;q=75 1x,
    /_next/image?url=%2Fbird.jpg&amp;w=3840&amp;q=75 2x
  "
  src="/_next/image?url=%2Fbird.jpg&amp;w=3840&amp;q=75"
/>

sizes について

srcset でデバイスによってダウンロードする画像を変えることができましたが sizes を利用することでより柔軟にダウンロードする画像を変えることができます。

fill 属性を設定するとブラウザのコンソールに sizes に関するメッセージを確認することができます。


warn-once.js:16 Image with src "/bird.jpg" has "fill" but is missing "sizes" prop. Please add it to improve page performance. Read more: https://nextjs.org/docs/api-reference/next/image#sizes

img タグの結果を再度確認すると sizes の値はデフォルトで 100vw が設定されています。vw は”viewport width”の略です。


<img
  alt="a bird"
//略
  sizes="100vw"
  srcset="
    /_next/image?url=%2Fbird.jpg&amp;w=640&amp;q=75   640w,
    /_next/image?url=%2Fbird.jpg&amp;w=750&amp;q=75   750w,
    /_next/image?url=%2Fbird.jpg&amp;w=828&amp;q=75   828w,
    /_next/image?url=%2Fbird.jpg&amp;w=1080&amp;q=75 1080w,
    /_next/image?url=%2Fbird.jpg&amp;w=1200&amp;q=75 1200w,
    /_next/image?url=%2Fbird.jpg&amp;w=1920&amp;q=75 1920w,
    /_next/image?url=%2Fbird.jpg&amp;w=2048&amp;q=75 2048w,
    /_next/image?url=%2Fbird.jpg&amp;w=3840&amp;q=75 3840w
  "
  src="/_next/image?url=%2Fbird.jpg&amp;w=3840&amp;q=75"
/>

Image コンポーネントでは props として sizes の値を変更することができるので下記のように sizes を 50vw に設定を変更してみましょう。


import Image from 'next/image';

export function NextImage() {
  return (
    <div
      style={{
        position: 'relative',
        width: '300px',
        height: '300px',
      }}
    >
      <Image
        src="/bird.jpg"
        alt="a bird"
        fill
        style={{ objectFit: 'contain' }}
        sizes="50vw"
      />
    </div>
  );
}

ブラウザで確認すると見た目の違いはわかりませんが sizes の値を変更するとその値に合わせて srcset の値も更新されます。デベロッパーツールを利用して要素を確認すると」先程まで存在しなかった w=384 が追加されています。



<img alt="a bird" loading="lazy" decoding="async" data-nimg="fill"
  style="position:absolute;height:100%;width:100%;left:0;top:0;right:0;bottom:0;object-fit:contain;color:transparent" sizes="50vw"
  srcset="
    /_next/image?url=%2Fbird.jpg&w=384&q=75 384w,
    /_next/image?url=%2Fbird.jpg&w=640&q=75 640w,
    /_next/image?url=%2Fbird.jpg&w=750&q=75 750w,
    /_next/image?url=%2Fbird.jpg&w=828&q=75 828w,
    /_next/image?url=%2Fbird.jpg&w=1080&q=75 1080w,
    /_next/image?url=%2Fbird.jpg&w=1200&q=75 1200w,
    /_next/image?url=%2Fbird.jpg&w=1920&q=75 1920w,
    /_next/image?url=%2Fbird.jpg&w=2048&q=75 2048w,
    /_next/image?url=%2Fbird.jpg&w=3840&q=75 3840w"
  src="/_next/image?url=%2Fbird.jpg&w=3840&q=75"
>

w=384 のファイルではさらに画像が小さくなり、画像の容量は 8.4KB となりました。

50vw にしてもブラウザ上に表示させれている画像の違いがわかりませんでしたが実はダウンロードするサイズが変わっています。デバイスピクセル比 1 でブラウザ幅が 1000px の場合は 1080w の画像をダウンロードすると説明しましたがそれは sizes が 100vw の場合で 50vw に変更すると 640w の画像をダウンロードするようになります。つまりブラウザ幅の半分に対応する画像をダウンロードするようになります。

50vw はどういう時に利用するのか?という疑問を持つ人もいるかと思います。たとえば grid を利用して画像を 2 列に表示している場合などに利用できます。この場合は画像の幅はブラウザ幅いっぱいに表示させる必要がないため大きな画像が必要ありません。2 列に設定を行っているため画像のサイズもブラウザ幅の半分である方が最適な画像になります。そのため sizes で 50vw を設定してブラウザ幅の半分の画像がダウンロードできるように設定しています。


import Image from 'next/image';

export function NextImage() {
  return (
    <div
      style={{
        display: 'grid',
        gridTemplateColumns: '1fr 1fr',
        height: '300px',
      }}
    >
      <div
        style={{
          position: 'relative',
        }}
      >
        <Image
          src="/bird.jpg"
          alt="a bird"
          fill
          style={{ objectFit: 'cover' }}
          sizes="50vw"
        />
      </div>
      <div
        style={{
          position: 'relative',
        }}
      >
        <Image
          src="/bird.jpg"
          alt="a bird"
          fill
          style={{ objectFit: 'cover' }}
          sizes="50vw"
        />
      </div>
    </div>
  );
}
Gridを設定し、sizesを変更
Gridを設定し、sizesを変更

sizes ではメディアクエリを利用することができます。Next.js の Image コンポーネントのドキュメントに掲載されている例ですがレスポンシブデザインでアクセスするデバイスによって 1 列、2 列、3 列と分けている場合には下記のように sizes にメディアクエリを設定することでデバイスによって最適なファイルサイズの画像をダウンロードすることができます。


<Image
  fill
  src="/bird.jpg"
  sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>

srcset と sizes の設定によりアクセスするデバイスによって最適なサイズの画像をダウンロードさせることができることがわかりました。

quality の設定

最適化する画像の quality を設定することができます。設定値は 1 から 100 を設定することができ、デフォルトでは 75 に設定されています。quality の値については srcset の各画像のパラメータに q=75 として設定が行われています。


/_next/image?url=%2Fbird.jpg&amp;w=3840&amp;q=75 3840w"

quality 設定の違いをはっきりとわかるように 1 を設定します。


import Image from 'next/image';

export function NextImage() {
  return (
    <div
      style={{
        position: 'relative',
        width: '600px',
        height: '600px',
      }}
    >
      <Image
        src="/bird.jpg"
        alt="a bird"
        fill
        style={{ objectFit: 'contain' }}
        quality={1}
      />
    </div>
  );
}

画像のサイズは w が 2048 の場合でも 12.2kb となります。ブラウザ上で見ても背景がかなり粗くなっていることが確認できます。

Qualityを1に設定
Qualityを1に設定

このように表示させる画像の質も quality によって調整することができます。quality の値を下げると画質が下がりますがファイルサイズも小さくなるのでサイトの特性によって適切な値を見つけることができればより高速なサイトにすることができます。

placeholder(Blur)

リモートサイトの画像を利用している場合の placeholder による blur, blurDataURL の設定方法を確認していきます。ドキュメントのplaceholderを確認するとPlaiceholderのページへのリンクが貼られているので Plaiceholder を利用します。

パッケージのインストール

plaiceholder のバッケージのインストールを行います。


% npm install plaiceholder

リモートサイトの画像

plaiceholder を利用して base64 の作成の前に placeholder 属性に blur を設定して動作確認を行います。


import Image from 'next/image';

export async function NextImage() {
  return (
    <Image
      src="https://reffect.co.jp/images/bird.jpg"
      width={1280}
      height={852}
      alt="a bird"
      priority
      placeholder="blur"
    />
  );
}

ブラウザでアクセスすると以下のメッセージが表示されまう s。placeholder の値を blur に設定した場合には blurDataURL 属性の設定が必要になります。


Error: Image with src "https://reffect.co.jp/images/bird.jpg" has "placeholder='blur'" property but is missing the "blurDataURL" property.
        Possible solutions:
          - Add a "blurDataURL" property, the contents should be a small Data URL to represent the image
          - Change the "src" property to a static import with one of the supported file types: jpeg,png,webp,avif (animated images not supported)
          - Remove the "placeholder" property, effectively no blur effect
        Read more: https://nextjs.org/docs/messages/placeholder-blur-data-url

placeholder を利用して画像を元に base64 を作成しますが作成方法は plaiceholder のドキュメントを参考にします。

ドキュメントの設定方法を参考
ドキュメントの設定方法を参考

import Image from 'next/image';
import { getPlaiceholder } from 'plaiceholder';

export async function NextImage() {
  const src = 'https://reffect.co.jp/images/bird.jpg';

  const buffer = await fetch(src).then(async (res) =>
    Buffer.from(await res.arrayBuffer())
  );

  const { base64 } = await getPlaiceholder(buffer);
  return (
    <Image
      src="https://reffect.co.jp/images/bird.jpg"
      width={1280}
      height={852}
      alt="a bird"
      priority
      placeholder="blur"
      blurDataURL={base64}
    />
  );
}

更新後ブラウザからページにアクセスすると画像がダウンロードされるまで下記の画面が表示されます。

blur画面
blur画面

ローカル画像

ローカルの public ディレクトリに保存している bird.jpg ファイルの場合は fetch 関数ではなく Node の fs/promise 関数でファイルを読み込んで処理を行います。


import Image from 'next/image';
import fs from 'node:fs/promises';
import { getPlaiceholder } from 'plaiceholder';

export async function NextImage() {
  const file = await fs.readFile('./public/bird.jpg');

  const { base64 } = await getPlaiceholder(file);
  return (
    <Image
      src="https://reffect.co.jp/images/bird.jpg"
      width={1280}
      height={852}
      alt="a bird"
      priority
      placeholder="blur"
      blurDataURL={base64}
    />
  );
}

import 画像

画像を import した場合には blurDataURL が自動で設定されます。


import Image from 'next/image';
import Bird from '../../../public/bird.jpg';

export async function NextImage() {
  return <Image src={Bird} alt="a bird" priority placeholder="blur" />;
}

表示される Blur の画面については先ほどの plaiceholder で作成したものとはわずかに異なることがわかります。

import画像のblur
import画像のblur

まだこの他にも Image コンポーネントのパラメータは存在しますがここまで理解できれば Next.js の Image コンポーネントだけではなく一般的な画像についての理解も深まっていると思いますのでぜひ開発に活かしてください。