Image コンポーネントの特徴
Next.js が提供している Image コンポーネント (next/image
) を使用すると、image
要素をそのまま配置するのに比べて次のような恩恵を受けられます。
- 遅延ロード (Lazy loading)
- Web ブラウザでその画像がビューポート内(画面内)に入って来たときに初めてダウンロードされるようになります。大きなページの末尾部分に配置された画像が、無駄にダウンロードされてしまうのを防ぐことができます。
- 画像の最適化
- アクセスしてきたクライアントに応じて画像ファイルを最適化して配信します。例えば、圧縮効率のよい WebP フォーマットなどに変換してくれます。リクエスト時にサーバーサイドでオンデマンドで最適化するため、
Image
コンポーネントを使うことでビルド時間が伸びてしまうことはありません。外部サーバーの画像を間接的に表示する場合も最適化できます。
- アクセスしてきたクライアントに応じて画像ファイルを最適化して配信します。例えば、圧縮効率のよい WebP フォーマットなどに変換してくれます。リクエスト時にサーバーサイドでオンデマンドで最適化するため、
- レスポンシブ
- 画面サイズに応じたレスポンシブ表示 (CSS) がデフォルトで行われます。
画像最適化に関しては、Next.js サーバー上でホスティングしているときしか動作しないといった制約がありますが、遅延ローディングがデフォルトで有効になるのは便利です。
Image コンポーネントの基本的な使い方
次のサンプルコンポーネントでは、Image
コンポーネントを使って /public/images/avatar.png
ファイルを表示しています。
public
ディレクトリ以下に配置したファイルは、Web サイトへのアクセス時には、ルートパス (/
) からの相対パスで参照できるようになります。
public
ディレクトリは、必ず Next.js プロジェクトのルートディレクトリに配置する必要があります。
.ts
、.tsx
ファイルは src
ディレクトリ以下にも配置できますが、public
ディレクトリは必ずルートに置かなければいけません。width
や height
プロパティを明示しておくと、画像のロード前にそのサイズの枠が確保されることになります。
これは、CLS (Cumulative Layout Shift) を発生させないために重要です。
表示サイズがあらかじめ決まっていないと、画像ロード後にレイアウトがガクッとずれたりして UX が低下します。
Google によるサイト評価でも CLS スコアは重要視されています。
Image コンポーネントによる画像の拡大・縮小
指定したサイズに拡大・縮小 (width, height)
一番基本的な使い方です。
Image
コンポーネント内に表示される画像は、デフォルトで Image
コンポーネント自体のサイズ、つまり、width
、height
プロパティで指定したサイズに拡大・縮小されて表示されます。
画像ファイルのアスペクト比を考慮して指定しないと、上の例のように変形して表示されてしまうので注意してください。
ちなみに、Image
コンポーネントが出力する img
要素には、max-width: 100%
という CSS スタイルが設定されているため、width
プロパティで指定する数値が親コンポーネントの横幅を超えていてもうまく収まるように表示してくれます。
アスペクト比を保って大きく表示 (objectFit=contain)
Image
コンポーネントの objectFit プロパティ に contain
を指定すると、画像のアスペクト比を保ちながら、縦か横にできるだけ大きく表示してくれます。
この objectFit
プロパティは、HTML の img
要素用の CSS プロパティである、object-fit プロパティ そのものです。
このプロパティのデフォルト値は fill
なので、これを指定しない場合は、画像が width
、height
プロパティで指定された通りに引き伸ばして表示されるというわけです。
親コンポーネントのサイズに連動させる (layout=fill)
Image
コンポーネントの width
、height
プロパティを指定する代わりに、layout プロパティ を fill
に設定すると、親要素のサイズが Image
コンポーネントのサイズとして使われます。
上記の例でいうと、上の階層に配置した div
要素のサイズが、Image
要素のサイズになります。
ここでは、div
要素自体の横幅も、さらにその親の要素の横幅に合わせるように 100%
指定しています。
Image
コンポーネントのサイズを layout="fill"
で親要素のサイズに合わせるときは、objectFit="contain"
も一緒に指定して、アスペクト比を保って拡縮表示するとよいです。
これを指定しないと、デフォルトの objectFit="fill"
が使われて、画像が親要素の形に変形して表示されてしまいます。
(応用)環境によって画像ファイルのダウンロード先を変える (NODE_ENV)
開発環境では /public
ディレクトリ以下の画像リソースを参照し、本番環境では別のサーバー上の画像リソースを参照する、といったことをやりたい場合は、例えば NODE_ENV
環境変数の値で条件分岐させます。
process.env.NODE_ENV
の値は、実行環境によって次のように変化します。
NODE_ENV の値 | 実行環境の例 |
---|---|
production | 本番サーバー (next start ) |
development | 開発サーバー (next dev ) |
test | テスト (jest ) |
次の関数は、実行環境に応じて画像ファイルの URL を作り分けます。
あとは、次のように Image
コンポーネントなどで使います。
ちなみに、Next.js の Image
コンポーネントの src
プロパティで、外部サーバー上の画像ファイルを指定する場合は、next.config.js
でドメイン名を指定しておく必要があります。
他にも、next.config.js
ファイル内で assetPrefix
の値を切り替えることで、画像ファイルの取得先を切り替える方法もあります。
これであれば、Image
コンポーネントの src
プロパティは次のようにシンプルに記述できます。
単純に URL のプレフィックス部分だけ切り替えられればよいのであれば、こちらの方法を使った方が簡単かもしれません。
ただし、assetPrefix
の設定は、next/image
が提供する Image
コンポーネントを使った場合にしか効果がないことに注意してください(生の img
コンポーネントをそのまま使った場合はプレフィックスが反映されません)。
また、assetPrefix
は全てのリソースの参照パスを切り替えてしまうので、特定のリソースのみ別のサーバーにおきたい場合などは、前述の getImageSrc
関数のような独自の関数を用意したり、Image
や img
をラップするコンポーネントを作成する必要があります。
(応用)src で指定した画像が見つからなかったときにプレースホルダー画像を表示する
Next.js の Image
コンポーネントに限らず、通常の HTML の img
要素でも同じなのですが、src
属性で指定した画像ファイルが見つからなかったときは、
のようなアイコンが表示されます(どう表示されるかは Web ブラウザによって異なります)。
このようなケースでは、Image
コンポーネントの onError
prop に設定したコールバック関数が呼ばれるようになっているので(img
の場合は onerror
)、この中で代わりの画像の URL をセットするということができます。
次の例では、指定した URL の画像が存在しなかったときに、プレースホルダー画像を表示するように src
の URL を変更しています。
function renderImage(title: string, url: string): JSX.Element {
return (
<Image
src={url}
unoptimized={true}
layout="fixed"
width={300}
height={200}
title={title}
alt={title}
onError={(e) => {
e.currentTarget.src = `https://placehold.jp/32/003060/e0e0e0/300x200.png?text=${title}`
}}
/>
)
}
const MyPage: NextPage= () => {
return (
<>
{renderImage('画像タイトル', 'https://example.com/404.png')}
</>
)
}
export default MyPage
ここでは、プレースホルダー画像を自動生成するために、Placehold.jp を使わせてもらっています。 プレースホルダー生成サービスとしては、他にも Placeholder.com などがありますが、こちらは日本語テキストの表示に対応していないようです。 次のような URL でアクセスするだけで、指定したサイズ、カラー、テキストのプレースホルダー画像を返してくれます。
- https://placehold.jp/32/003060/e0e0e0/300x200.png?text=Title
- https://via.placeholder.com/300x200/003060/e0e0e0.png?text=Title
こういうサービスはとってもありがたいです。感謝 ٩(๑❛ᴗ❛๑)۶
外部サービスに頼りたくないのであれば、次のようにクライアントサイド JavaScript でプレースホルダー画像を生成してしまう方法もあります。
/**
* プレースホルダー画像を動的に生成して、img 要素の src にセットするための URL を返します。
* `document` オブジェクトを参照するため、クライアントサイド JS でのみ実行可能です。
*/
function createPlaceHolderImage(
width: number,
height: number,
text: string
): string {
// Canvas 要素を動的に生成
const canvasElem = document.createElement('canvas')
canvasElem.width = width
canvasElem.height = height
const ctx = canvasElem.getContext('2d') as CanvasRenderingContext2D
// 背景を描画
ctx.fillStyle = '#003060'
ctx.fillRect(0, 0, width, height)
// テキストを描画(中央寄せ)
ctx.fillStyle = 'white'
ctx.font = `bold 24px Arial, meiryo, sans-serif`
const textWidth = ctx.measureText(text).width // 左右の中央寄せ用
ctx.textBaseline = 'middle' // 上下の中央寄せ用
ctx.fillText(text, (width - textWidth) / 2, height / 2, width)
// 描画内容をデータ化して URL にして返す
return canvasElem.toDataURL()
}
関数内で document
オブジェクトを参照しているため、ビルド時に実行されないように注意してください。
次のように、クライアントサイド JS でしか実行されないようになっていれば大丈夫です(useEffect
の中で呼び出すのも大丈夫です)。
<Image
...
onError={(e) => {
e.currentTarget.src = createPlaceHolderImage(300, 200, '画像タイトル')
}}
/>
関連記事
- Next.js で Sass (scss/sass) を有効にする
- Next.js ですべてのページにグローバルな CSS を適用する (pages/_app.ts)
- Next.js でコンポーネント単位の CSS を作成する (CSS Modules)
- Next.js の API Routes 機能で Web API を作成する
- Next.js のダイナミックルーティング機能を利用する (getStaticPaths, getStaticProps, getServerSideProps)
- Next.js でコンポーネント内に直接 CSS を記述する (styled-jsx)
- Next.js で全ページ共通のレイアウトを定義する(Layout コンポーネント)