【Next.js14】next/imageによる画像パフォーマンス最適化(blur・lazyローディング)
はじめに
今回は、Next.jsを使用して画像をblurローディングする方法についてまとめます。
また、デモ動画では高速表示捺せ対画像とそうでないblurの画像の表示比較を行い、違いを確認しています。
前提
今回の実装では、MicroCMS
からデータを取得しています。
ただ、個人的には、動的データの場合、next/image
のblurローディングではなく、Suspence
で囲って、画像も含めたプロパティ全てをローディングにしてから表示する実装をすることをおすすめします。
blurローディングはLPなど画像をそれなりに使用する場合で、初期表示時に画像が表示されるが、高速表示の優先度は低い場面等で使用してください。
(ユーザーに画像を読み込んでいるという認識を与えるために使用してください)
まとめると、画像のパス(src部分の値)がはっきりしている静的ファイルに使用することを私は推奨いたします。
(動的ファイルには上記のようにSuspenseを使用したほうがWebアプリでは良いと考えているからです)
※ インスタグラムなど、画像メインのアプリを作成する場合などは画像表示のblurローディングはありなので、原則Supenseという認識です
パフォーマンス最適化について
先程、以下のようなことを説明しました。
blurローディングはLPなど画像をそれなりに使用する場合で、初期表示時に画像が表示されるが、高速表示の優先度は低い場面等で使用してください。
(ユーザーに画像を読み込んでいるという認識を与えるために使用してください)
パフォーマンス最適化のために追加で解説すると、スクロールしないと見えない画像についてLazyローディングさせるのがよいです。
(next/imageの場合は、priority
というpropsを追加しない場合、すべてlazyローディングになるようです)
まとめると、ユーザーに「この画像は確実に表示されないといけない」画像についてはpriority
を付け、「画像表示はするが画像のローディングである」ということを示したい場合は、blur ローディング
、「そもそもスクロールしないと画像があることすらわからない」という画像についてはlazy ローディング
というような使い分けをすると良いということです。
ちなみに、コンポーネントをlazyローディングするには、React.lazy
を使用します。
このようにすることでブラウザに送られるJSのバンドルサイズを下げることができます。
余談ですが、React19からはRSCでReact.Lazy
が実現できるようですし、他にも実装が楽になったり、実装の幅が広がるようなアップデートがあるようです。
(とはいえ、React.Lazy
実装しないから知識いらないということではなく歴史を知っておくと実装の際にバッドプラクティスを踏むことがなくなるのでコーディングで不要でも、このようなことは積極的に調べたほうがよいと私は思っています)
環境
- next 14.1.0
- sharp 0.33.2
- plaiceholder 3.0.0
- microcms-js-sdk 2.7.0
- tailwind-variants 0.1.20
実装
さて、前提が長くなりましたが、実装の解説に移ります。
実装の全体ですが、以下のように実装しました。
(コードを抜粋し、順に解説します)
import { getAllBooks } from '@/libs/microcms'
import { BookType } from '@/types/Book'
import { MicroCMSListResponse } from 'microcms-js-sdk'
import Image from 'next/image'
import { getPlaiceholder } from 'plaiceholder'
import { tv } from 'tailwind-variants'
const home = tv(
{
slots: {
base: 'flex flex-wrap justify-center items-center mt-20',
},
variants: {
size: {
md: { base: 'mt-32' },
},
},
},
{ responsiveVariants: ['md'] },
)
const Home = async () => {
const { base } = home({ size: { md: 'md' } })
const { contents: books } =
(await getAllBooks()) satisfies MicroCMSListResponse<BookType>
if (!books[0].thumbnail?.url) {
return null
}
const response = await fetch(books[0].thumbnail.url)
const arrayBuffer = await response.arrayBuffer()
const buffer = Buffer.from(arrayBuffer)
const { base64 } = await getPlaiceholder(buffer)
return (
<main className={base()}>
<Image
src="/default_icon.png"
width={200}
height={200}
priority={true}
alt="test"
/>
<Image
src={books[0].thumbnail.url}
width={200}
height={200}
placeholder="blur"
blurDataURL={base64}
alt="test"
/>
</main>
)
}
export default Home
まず、blurローディングするためには、next/imageのImageコンポーネントのplacehlder
というpropsに"blur"
を指定するだけでできます。
ただし、上記のpropsのみの指定だけではnext/imageのエラーで「blurDataURL
がないよ」というエラーが出ます。
このblurDataURL
にはblurローディングに表示させたい画像のsrcを指定します。
つまり、本来表示すべき画像を解像度を下げた状態にして、その解像度を下げた画像のURLを渡す必要があるということです。
もう少しわかりやすく解説します。
例えば上記の実装のbooks[0].thumbnail.url
のblurローディング時に、別の画像を表示する場合を考えてみましょう。
この場合、例えばblurDataURL
にhoge.png
という静的画像を指定するとします。
この例でいうと、books[0].thumbnail.url
のblurローディング時には、hoge.png
が表示され、books[0].thumbnail.url
の読み込みが終わると、books[0].thumbnail.url
が表示されます。
要するに、placeholder="blur
とblurDataURL
はセットで使用して、初めてblurローディングが実現できるということです。
それでは、抜粋したコードを見ていきましょう。
低解像度画像に変換する処理
このコードでは、books[0].thumbnail.url
をバイナリデータに変換して、plaiceholder
というライブラリでbase64
文字列にエンコード(変換)された低解像度の画像に変換しています。
const response = await fetch(books[0].thumbnail.url)
const arrayBuffer = await response.arrayBuffer()
const buffer = Buffer.from(arrayBuffer)
const { base64 } = await getPlaiceholder(buffer)
まずは、ライブラリの導入ですが、公式にあるようにインストールしていきます。
(plaiceholder
はsharp
というライブラリで画像変換するためsharp
のインストールも必要なようです)
pnpm add sharp plaiceholder
さらに、上記コードを詳細に解説すると、まず、文字列である画像URL(books[0].thumbnail.url)をBuffer型(バイナリデータ)にするためfetchしてレスポンスを取得します。
これにより、responseのarrayBuffer()
を使用できます。
さらに、arrayBuffer()
によりresponseのBuffer(バイナリデータ)形式の部分を取得することができます。
ちなみに、ArrayBuffer は、バイナリデータを格納するための一般的な固定長バッファのことを指します。そのため、このメソッドは、画像やバイナリファイルなど、テキスト以外のデータを扱う際に便利です。
次に、Buffer.from
を使用してArrayBufferをBufferオブジェクトに変換します。 Bufferオブジェクトに変換することで
plaiceholderで
books[0].thumbnail.url`を扱えるようしています。
あとは、plaiceholder
から、base64文字列に変換した低解像度の画像を取得するだけです。
今回はわかりやすくするため、フェッチからBufferの変換を分けましたが、pliceholder
公式のようにまとめて書くこともできます。
Imageコンポーネント
最後に、画像の表示ですが、blurローディングするためのpropsに対して、値を渡すだけです。
具体的には以下となります。
- placeholder: "blur"
- blurDataURL: base64
これを設定するだけで、blurローディングができます。
import Image from 'next/image'
import { getPlaiceholder } from 'plaiceholder'
const Home = async () => {
// ※ 関連する処理のみ抜粋のため、import文含め省略しています
const response = await fetch(books[0].thumbnail.url)
const arrayBuffer = await response.arrayBuffer()
const buffer = Buffer.from(arrayBuffer)
const { base64 } = await getPlaiceholder(buffer)
return (
<main className={base()}>
<Image
src="/default_icon.png"
width={200}
height={200}
priority={true}
alt="test"
/>
<Image
src={books[0].thumbnail.url}
width={200}
height={200}
placeholder="blur"
blurDataURL={base64}
alt="test"
/>
</main>
)
}
デモ
以下がデモになります。
※ ご自身で実装して検証する際はDeveloper Toolのネットワークで「キャッシュを無効」にチェックをし、「高速3G」または「低速3G」で実装の確認をすることを推奨します
参考文献
Discussion