next/image を利用した画像最適化で CWV の数値を改善する
この記事は最終更新日から2年以上が経過しています。
概要
imgurにあげている画像を参照しているが、最適に表示できていなかったのでCWVの数値が悪い。next/imageの機能を使って最適化してみる。改善ポイントは以下
- next/image を使った画像の最適化
- 画像サイズの最適化
- preload と lazyload の使い分け
改善前後の変化
改善前
改善後
画像の最適化
webpを使って軽量な画像にしてたつもりだったが、LCPやCLSの値が悪い。 next/image の CustomLoader を作って imgur の画像を最適化することで解決する
変更前のコード(抜粋)
srcSet を使って最適な画像を使うように設定してるが、Layout Shift が起きる next/image の CustomLoader を使えば良さそうなことはわかっていたが、やり方がわからなかったので一旦放置していた。
// cover-image.tsx
import cx from 'classnames'
import Link from 'next/link'
type Props = {
title: string
src: string
slug?: string
}
const CoverImage = ({ title, src, slug }: Props) => {
const srcSet = [
`${src}h.webp 1024w`,
`${src}l.webp 640w`,
`${src}m.webp 320w`
]
const image = (
<picture>
<source
type="image/webp"
className="max-w-sm md:max-w-md lg:max-w-lg xl:max-w-xl 2xl:max-w-2xl"
srcSet={srcSet.join(',')}
/>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={`${src}l.jpeg`}
alt={`Cover Image for ${title}`}
loading="lazy"
className={cx('shadow-sm', {
'hover:shadow-lg transition-shadow duration-200': slug
})}
/>
</picture>
)
return (
<div className="sm:mx-0">
{slug ? (
<Link as={`/posts/${slug}`} href="/posts/[slug]">
<a aria-label={title}>{image}</a>
</Link>
) : (
image
)}
</div>
)
}
export default CoverImage
変更後のコード(抜粋)
next/image を使うように修正した。CustomLoaderのコードは後述する。 画像の高さを固定することにより、Layout Shift が起きないようになっている。 これでCLSの数値を改善できる。
また、Hero Image として画像を使う場合は preload するように設定した。これでLCPも改善できる。( PageSpeed Insights のアドバイス通りにやっただけなので特に難しいことはない )
// cover-image.tsx
import cx from 'classnames'
import Image from 'next/image'
import Link from 'next/link'
import { customLoader } from '../lib/image-loader'
type Props = {
title: string
src: string
slug?: string
isHero?: boolean
}
const CoverImage = ({ title, src, slug, isHero }: Props) => {
// HeroImageとして使うかそうでないかでサイズを分ける
const height = isHero ? 'clamp(200px,50vw,1000px)' : 'clamp(200px,30vw,500px)'
const image = (
<figure
style={{ position: 'relative', height: `${height}` }}
className={cx('relative shadow-sm w-full', {
'hover:shadow-lg transition-shadow duration-200': slug
})}
>
<Image
loader={customLoader}
src={src}
alt={`Cover Image for ${title}`}
layout="fill"
objectFit="contain"
priority={isHero} // hero image のときは preload する
/>
</figure> )
return (
<div className="sm:mx-0">
{slug ? (
<Link as={`/posts/${slug}`} href="/posts/[slug]">
<a aria-label={title}>{image}</a>
</Link> ) : (
image
)}
</div>
)
}
export default CoverImage
上で使っている CustomLoader 最低限の実装だけしている。 imgurの画像でない場合はそのまま使い、imgurの画像の場合は表示幅に合わせて適切な大きさの thumbnail が表示されるURLを生成するようになっている
// image-loader.ts
import { ImageLoader } from 'next/dist/client/image'
export const customLoader: ImageLoader = ({ src, width, quality }) => {
if (!src.includes('https://i.imgur.com')) return src
if (width >= 1024) return `${src}h.webp`
if (width >= 640) return `${src}l.webp`
return `${src}m.webp`
}