next/imageを使ってビルド時に画像を最適化する方法
はじめに
2022 年 5 月現在、next/image
を next export
で使う場合、
画像プロバイダーを使って外部 URL を利用する方法しかありません。
(next/image
のデフォルトは、Nodejs サーバーを使用した画像最適化 API のもと行われるため)
しかし、シンプルなウェブサイトを構築する場合や Node.js サーバーを使用せずに構築したい場合にこれは不便です。
よってこの問題を解決するためのライブラリを開発しました。
next/image
を使ってビルド時に画像を最適化するソリューションをお探しの方はこちらをぜひお使いください!
- GitHub
- ドキュメントサイト
特徴
主な特徴としまして、以下があります。
- ビルド時に画像を最適化
-
next/image
の全オプションが利用可能 - 拡張子の変換が可能
-
sharp
を使用し高速に動作 - キャッシュにより同じ最適化の繰り返しを防止
使い方
- パッケージをインストールします。
yarn add -D next-export-optimize-images
-
next.config.js
にプラグインを追加します。
const withExportImages = require('next-export-optimize-images')
module.exports = withExportImages({
// write your next.js configuration values.
})
-
package.json
のビルドコマンドを変更します。
{
- "export": "next build && next export",
+ "export": "next build && next export && next-export-optimize-images",
}
- 通常通り
next/image
を使用します。
import Image from 'next/image'
<Image src="/images/img.png" width={1920} height={1280} alt="" />
// Or import as follows
<Image src={require('./img.png')} alt="" />
設定
デフォルトの動作は必要に応じて変更可能です。
ルートに export-images.config.js
を作成します。
/**
* @type {import('next-export-optimize-images').Config}
*/
const config = {
// your configuration values.
}
module.exports = config
詳細はこちらのドキュメントサイトをご覧ください。
使用例
ここではいくつか使用例を紹介していきます。
ただ、next/image
の使い方と基本的に同じですので、詳しくはこちらの公式ドキュメントをご覧ください。
placeholder
を使用する
<Image placeholder="blur" src="/images/img.png" width={1920} height={1280} alt="" />
// Or import as follows
<Image placeholder="blur" src={require('./img.png')} alt="" />
layout
を fill
に設定する
<Image layout="fill" objectFit="cover" src="/images/img.png" width={1920} height={1280} alt="" />
// Or import as follows
<Image layout="fill" objectFit="cover" src={require('./img.png')} alt="" />
loader
を設定する
独自の import { ImageLoaderProps, ImageProps } from 'next/image'
import { FC } from 'react'
type Props = ImageProps
const CMSLoader = ({ src, width, quality }: ImageLoaderProps) => {
const url = new URL(normalizeSrc(src))
const params = url.searchParams
params.set('auto', params.get('auto') || 'format')
params.set('fit', params.get('fit') || 'max')
params.set('w', params.get('w') || width.toString())
if (quality) {
params.set('q', quality.toString())
}
return url.href
}
const CMSImage: FC<Props> = (props) => {
return <Image loader={CMSLoader} {...props} />
}
export default CMSImage
内部構造
ここからは内部でどういった処理をしているのかが気になる人向けに内部構造を紹介します。
ライブラリを使用するために必要な知識ではないのでご安心ください。
処理手順
まず、どのような処理手順があるのか、大まかに説明します。
-
next/image
をインポートする際に、next/image
をラップするカスタムコンポーネントをこのライブラリから自動的に読み込むようにwebpack
の設定を変更する。 -
next/image
のloader
から最適化する画像の情報を受け取り、JSON ファイルに書き出す。 -
next export
の後、先ほどエクスポートした JSON ファイルに基づいて画像を最適化する。
また、next dev
すると、loader
はほぼそのままの文字列を元の画像で返すので、ビルドに時間がかかりません。
ここからは、より深く解説していきます。
next/image
を取り込む際
import Image from "next/image";
このように next/image
コンポーネントをインポートすると、自動的に next-export-optimzie-images/dist/image
にエイリアスされます。
これは、webpack
のエイリアス機能を利用しています。 (https://webpack.js.org/configuration/resolve/#resolvealias)
next/image
の loader
をカスタマイズする。
このライブラリの画像コンポーネントは、カスタマイズされた loader
を内部で設定しています。
これは next/image
内で src
や srcSet
などを実際にレンダリングする際に利用されます。また、ビルド時には、最適化するイメージのリストを JSON ファイルに書き出します。
このとき、layout
属性、sizes
属性、placeholder
属性などを元に、最適化するイメージのリストが作成されます。
そのため、未使用の画像が生成されることはなく、ビルド時間が無駄に長くなることはありません。
例えば、以下のように 2 つの画像コンポーネントをレンダリングするとします。
<>
<Image src="/intrinsic.png" width={1280} height={640} alt="" />
<Image
src="/responsive.png"
width={1280}
height={640}
alt=""
layout="responsive"
/>
</>
このとき生成される画像は以下の通りです。
intrinsic_1280_75.png
intrinsic_2560_75.png
responsive_640_75.png
responsive_750_75.png
responsive_828_75.png
responsive_1080_75.png
responsive_1200_75.png
responsive_1920_75.png
responsive_2048_75.png
responsive_3840_75.png
画像最適化
next-export-optimzie-images
を実行して、画像の最適化を開始します。
これは基本的に yarn build && yarn export
の後に実行されます。
エクスポートされた JSON ファイルの情報を元に、前述の loader を通して画像が最適化されます。
このとき、最適化された画像はキャッシュデータ(画像ハッシュとファイルパス)と共に node_modules/.cache
ディレクトリに一旦保存されます。
2 回目以降の最適化では、この情報を元に最適化をスキップするかどうかが決定されます。
その仕組みは次の通りです。
キャッシュデータを格納した JSON ファイルから、同じファイルパスの画像を検索する。
- ある場合... → その画像のハッシュと比較し、違えばハッシュを更新して最適化した画像を作成。同じ場合は最適化をスキップする。
- ない場合... → その画像のハッシュとファイルパスをキャッシュデータに格納し、最適化された画像を作成する。
おわりに
Next.js は Web アプリケーションの開発はもちろんのこと、
その使いやすさから静的サイトでも使用したくなると思います。
その際に画像の最適化で困ったらぜひこのライブラリを使用してくださればと思います!
このライブラリについての問題や機能の提案などがあれば遠慮なく
Issue や僕の Twitter にどしどしメッセージください!
Discussion