これでわかるNext.jsの画像最適化Imageコンポーネント
Next.js でアプリケーションを構築している人の中には画像が自動で最適化されると聞いているので Image コンポーネントを利用しているけど実際にどのような設定が行われているかあまりよくわかっていないという人もいるのではないでしょうか。本文書では Next.js の Image コンポーネントを利用することでどのような設定が行われるのか実際に Next.js を動かしながら確認しています。フロントエンドの最先端の JavaScript フレームワークの一つである Next.js の画像の最適化を理解することで現在画像の最適化のためにどのようなアプローチが行われているかということも理解することができます。
Image コンポーネントに限りませんが WEB サイトでの画像の最適化のポイントには 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”コマンドを実行するとブラウザ上に画像が表示されます。
ブラウザのデベロッパーツールで確認すると以下のように表示されます。
<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 を設定してくださいと表示されているので設定を行います。
<Image src="/bird.jpg" width={1280} height={852} alt="a bird" priority />;
priority の設定により Warning は解消されます。
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 を設定した場合は遅延読み込みが行われるずページを開くとビューポートに関係なく画像の読み込みが行われます。
LCP(Largest Contentfull Paint)はビューポート内で最もサイズが大きいコンテンツ要素の読み込みを計測するものなので priority を設定することで LCP に対する対策を行っていることがわかります。
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 属性は設定していません。
上記の設定のようにローカルの 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&w=1920&q=75 1x,
/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fbird.9810d6b2.jpg&w=3840&q=75 2x"
src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fbird.9810d6b2.jpg&w=3840&q=75"
>
画像を import した場合には width と height を省略することができましたが srcset、src 属性の違いを確認することができます。画像を import した場合には src のパラメータの url に設定されている値が下記の通りです。
src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fbird.9810d6b2.jpg&w=3840&q=75"
//URLをデコード
/_next/image?url=/_next/static/media/bird.9810d6b2.jpg&w=3840&q=75
import していない場合は下記のようになっています。
src="/_next/image?url=%2Fbird.jpg&w=3840&q=75"
//URLをデコード
/_next/image?url=/bird.jpg&w=3840&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&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"
/>
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>
);
}
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 を設定するとアスペクト比を保ったまま、画像が領域の中に収まるにように表示されます。横長の画像の場合は横一杯に画像が表示されます。
親要素の背景色を入れておくと横幅いっぱいに表示されていることがわかります。
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>
);
}
利用している画像が横長画像なので親要素の領域に入りきらなった両側の画像の一部は切り取られて表示されます。
画像を 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&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"
/>
srcset 各行には 640w, 750w といった数字が並んでいますが srcset を設定することでアクセスしてきたデバイスに合わせてダウンロードする画像を変えることができます。例えば現在使っているディスプレイのデバイスピクセル比が 1 でブラウザのウィンドウサイズの横幅が 1000px の場合は”url=%2Fbird.jpg&w=1080&q=75 1080w”で指定されている画像がダウンロードされます。デバイスピクセル比が 2 の場合は 2048w で指定されている画像をダウンロードされます。1000px のウィンドウサイズなのに 2048w の画像がダウンロードされる場合は利用しているディスプレイのデバイスピクセル比が 1 ではないので確認してみてください。
ダウンロードする画像によってどれだけ画像のサイズの違いがあるか確認すると以下のような結果になりました。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
サイズによってかなり差があることがわかります。もし 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&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"
/>
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&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"
/>
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>
);
}
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&w=3840&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 によって調整することができます。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}
/>
);
}
更新後ブラウザからページにアクセスすると画像がダウンロードされるまで下記の画面が表示されます。
ローカル画像
ローカルの 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 で作成したものとはわずかに異なることがわかります。
まだこの他にも Image コンポーネントのパラメータは存在しますがここまで理解できれば Next.js の Image コンポーネントだけではなく一般的な画像についての理解も深まっていると思いますのでぜひ開発に活かしてください。