Next.js: how to use next-optimized-images library

first
2020年9月17日からios14の配信が始まりましたね。Windows、Android & Chromeユーザなので、普段はiosリリースに気にもかけないのですが、今回からsafariがWebpに対応した様で。。。(後で書き直す
今回は、例えばjpgやpngをwebpに変換し、レスポンシブやLow Quality Image Placeholderに対応すると言った、最適化をした事を書きたい。
next-optimized-images
以降、next-optimized-imagesを"next-opti"と略す
画像の最適化方法はタスクランナーの中で画像圧縮プラグインを利用するなど複数あるが、今回はcyrilwanner/next-optimized-images
を利用する。
environment
- "node": "v14.5.0"
- "react": "16.13.1"
- "next": "9.3.5",
- "next-optimized-images": "^2.6.2"
canary版のv3もある。
setup
yarn add next-optimized-images
このパッケージに加え、自分が必要な機能にあったプラグインを入れる必要がある。取り敢えず、MozJPEGとOptiPNGに変換するプラグインを入れておく。
yarn add npm imagemin-mozjpeg imagemin-optipng
config
パッケージの方に各プラグインのデフォルト設定値が含まれているが、next.config.js
の中で設定を変更できる。下は自分のもので書きやすくするために、next-compose-plugins
を入れている。
自分のnext.config.js
// next.config.js
const withPlugins = require('next-compose-plugins')
const withPWA = require('next-pwa') // 今回は関係ない
const optimizedImages = require('next-optimized-images')
const nextOptimizedImagesConfig = {
imagesFolder: 'images',
imagesName: '[name]-[hash].[ext]',
handleImages: ['jpeg', 'png', 'webp'],
removeOriginalExtension: true,
optimizeImages: process.env.MODE_ENV !== 'development',
optimizeImagesInDev: false,
mozjpeg: { quality: 85, },
optipng: { optimizationLevel: 3, },
webp: { preset: 'default', quality: 85,},
}
module.exports = withPlugins(
[
[ withPWA, nextPwaConfig ], // 今回は関係ない
[ optimizedImages, nextOptimizedImagesConfig ],
],
)
usage
href (image path)
画像は通常の方法ではなく、<img src={require(../../example.jpg)} />
の様に相対パスで指定するが、md内で指定する時などは面倒なので、next.js.config
にパスのエイリアスを追加する。
最初はpublicのエイリアスを用意していたが、public配下にあるwebmや.icon、xmlなど全てのファイルの最適化をしようとして、warningが出るので、対応画像のみを所持するディレクトリの分だけをエイリアスに指定した。
原因はwebpackにある模様
- references:
// next.config.js
// ...
const { resolve } = require('path')
const nextConfig = {
webpack: (config) => {
config.resolve.alias['@public/assets'] = resolve(__dirname, 'public/assets')
return config
},
}
// ...
module.exports = withPlugins(
[ // ...
],
nextConfig
)
convert to webp
<!-- use webp-loader for image optimization to webp -->
yarn add webp-loader
また、imagemin-mozjpegやimagemin-optipng等はhref={require('../example.jpg')}
の様にすればプラグインが適用化されるが、その他はquery paramsで指定する必要がある。
export default () => (
<picture>
<source srcSet={require('./images/my-image.jpg?webp')} type="image/webp" />
<img src={require('./images/my-image.jpg')} />
</picture>
)
responsive image
yarn add responsive-loader sharp
resizeを可能にするresponsive-loarderはjimpとsharpが別に必要だが、jimpはREADME.md(↓)でディスられてる位なので、sharpを使う。
Requires the optional package responsive-loader (npm install responsive-loader) and either jimp (node implementation, slower) or sharp (binary, faster)
画像をリンクする際はrequire('./images/my-image.jpg?resize&sizes[]=300&sizes[]=600&sizes[]=1000')
の様に指定できるが、下の様にresponsive:sized:[]
と画像サイズ幅をglobal resize propertyとして指定できる。
// next.config.js
// ...
const nextOptimizedImagesConfig = {
// ...
responsive: {
adapter: require('responsive-loader/sharp'),
sizes: [640, 960, 1200, 1800],
disable: process.env.MODE_ENV === 'development'
},
// ...
}
module.exports = withPlugins(
[[ optimizedImages, nextOptimizedImagesConfig ],],
nextConfig
)
サンプルコード
※ require内でのsizes指定方法は下の様に複数ある。
// どれでも動く
const multi = require(`../../public/cat1200x.jpg?resize&sizes:[640,960,1200,1800]`)
// const multi = require('../../public/cat1200x.jpg?resize&sizes[]=640&sizes[]=960&sizes[]=1200&sizes=[1800]')
// const multi = require('../../public/cat1200x.jpg?resize&sizes[]=640,sizes[]=960,sizes[]=1200,sizes[]=1900')
export default () => (
<img
srcSet={multi.srcSet}
src={multi.src}
width={multi.width}
height={multi.height}/>
);
webp-loader と responsive-loader
現在のnext-optiはissueを抱えていて、例えばwebp変換のwebp-loaderと複数サイズ画像生成のresponsive-loaderのquery parmasをexample.jpg?webp?resize
の様に連ねて書くと動かない。根本的な解決はnext-opti v3で解決する模様。
2つを同時に動かすサンプルコード
export default function () {
const multiWebp = require(`../../public/cat1200x.jpg?resize&sizes:[640,960,1200,1800]&format=webp`)
return (
<img
srcSet={multiWebp.srcSet}
src={multiWebp.src}
width={multiWebp.width}
height={multiWebp.height} />
)
}
自分の場合
next.config.js の中で、responsive:{sizes: [640, 960, 1200, 1800],}
としてあるので、https://oriver.devでは下の様にcomponentを作って利用している。(一部略)
// src/components/general/OptimizedImages.tsx
export function OptimizedImages({ src, alt, imgStyle }) {
const multi = require(`@public/assets/${src}?resize`)
const multiWebp = require(`@public/assets/${src}?resize&format=webp`)
return (
<React.Fragment>
<picture>
<source srcSet={responsiveImageWebp.srcSet} type='image/webp' />
<img
src={responsiveImage.src}
srcSet={responsiveImage.srcSet}
width={responsiveImage.width}
height={responsiveImage.height}
/>
</picture>
</React.Fragment>
)
}
Low Qualy Image Placeholder
yarn add lqip-loader
export default function () {
return (
<img src={require('../../public/shirase.jpg?lqip')} />
<img src={require('../../public/shirase.jpg')} />
)
}
豪に居た時に撮影した、西豪州フリーマントルに停泊する砕氷艦しらせの画像に適用してみた。lqip(左)の方は10x7pxの925bに縮小されており、パフォーマンス的には問題が無さそう。更に filter:blur(10px) 辺りを掛けると更に良さそう。
lqip-loaderを使ったprogressive image loading の実装。
早い話がmedium風の画像表示をやってみる。まずはuseStateを使って、lazy loadの画像がloadされたら、lqpiのopacityを0にする方法。
import React, { useState } from 'react'
export default function () {
const [imageLoaded, setImageLoaded] = useState(false)
return (
<>
<div>
<img src={require(`../../public/shirase.jpg?lqip`)}
className='lqip' style={{opacity: imageLoaded ? 0 : 1}}
/>
<img src={require(`../../public/shirase.jpg`)}
onLoad={() => setImageLoaded(true)} loading='lazy'
/>
</div>
<style jsx>{`
div{
position: relative;
}
img{
width: 50%;
height: auto;
}
.lqip{
position: absolute;
top: 0; left: 0; z-index: 10;
filter: blur(10px);
transition: opacity 500ms cubic-bezier(0.4, 0, 1, 1);
}
`}</style>
</>
)
}
その他、backgroundに指定する方法など各種ありそうだけど、まあ。
last
いかがでしたか?
タスクランナーで画像最適化ライブラリを動かすてもありますが、next-optimized-imagesの場合だと最適化済み画像のhrefを書く手間が省けて良いのではないでしょうか。因みに筆者はlqipがあまり好きでないので、今のところ https://oriverk.dev で適用する予定はないです。
references
- cyrilwanner/next-optimized-images
- Mozilla - Progressive loading
- Medium - Progressively Loading Images In React
[TOC]