Cloud Runに引っ越したことで重いページとそうでないページがはっきりしてきた(以前は全てのページが重くて逆に気にならなかった)のだが、その際にLighthouseで調べているとPerformanceのスコアが非常に悪く、画像の読み込みにめちゃくちゃ時間がかかっていることに気づいた。
トップページの記事ランキングなどにはサムネイルを表示していたりするのだが、アップロードされた画像をそのまま表示していた。中には横幅2000とか3000以上の画像もあったりする。そのためそういう画像がいくつも同じページ内にあると当然読み込みに時間がかかるし、Wifiじゃない状況でスマホで見たりすると通信容量の消費も激しくなるだろうし全然ユーザーフレンドリーじゃない。
ということで丁度モチベーション切れだったこともあり、せっかく気になってきたので対応してみることにした。
アップロードされた画像は全てZeitのNowで運用している画像配信サーバーで配信している。
NowのエッジキャッシュでCloud Storage節約サーバー作成
ということで、これを改造してリサイズに対応できればよいのではないかと思った。具体的には通常の画像のURLの後ろに ?mw=700&mh=300
のように、パラメータを指定した場合だけリサイズするようにする形。
リサイズと言えばImageMagick(現在はgm?)かなと思ったが、一応調べてみたところsharpというパッケージも非常に人気があるらしかったのでそちらを使ってみることにした。
一番シンプルな形だとこんな感じでいける。BufferをリサイズしてBufferにするパターン。
const resized = await sharp(image).resize(width).toBuffer()
めちゃくちゃ簡単。ImageMagickだとサーバーに何かインストールしておかなきゃいけないんじゃないかとか色々気になったりするが、特にそういう必要もない。
CSSの object-fit: cover
を使っているところが結構あったので、その部分の表示を変えることなくリサイズする仕組みを作った。具体的には ?mw=700&mh=300
のパラメータを付けると、どちらかがはみ出してもいいのでその範囲でなるべく大きな画像にする方法(余白ができないようにする)
色々処理を作っていたが、これはパラメータを指定するだけでできた。
if (query.mw && query.mh) {
image = await sharp(image)
.resize(Number(query.mw), Number(query.mh), { fit: 'outside' })
.toBuffer()
}
こんな感じでfitを指定すると色々な形でのリサイズ方法があるため便利。
記事ページなどは許可する最大の横幅を指定し、それ以上だったらリサイズする形にした。
さすがにfitでこの条件指定までは出来なかったため、チェックは自分で行った。さすがに画像を変換する時にサイズの取得は必要になるため、多分サイズを取得する機能があるだろうと思ったがやはりあった。ということで下記のような処理を作った。metadataというメソッドでサイズや画像の形式など、色々な情報が取れる。
async function resizeByMaxWidth(image, maxWidth) {
const sharpImage = sharp(image)
const metadata = await sharpImage.metadata()
if (metadata.width < maxWidth) {
return image
}
return await sharpImage.resize(maxWidth).toBuffer()
}
ということで色々対応したが、スコアは全然改善しなかった。他にもAPIで取ってきたデータのユーザー画像のサイズが大きかったりして、対応しきれていない部分がある。
とはいえ、やろうと思えばあらゆるURLの画像を同様の配信サーバーを作ってプロキシすることで縮小できるため、他にも色々使い所はありそう(今回は自分用のリポジトリに実装しているので前述URL内で公開しているリポジトリには反映していない。機能的にもブレるため)。Nowなのでサーバーの管理も不要だしCDNキャッシュしてくれて高速だし本当に楽。
Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。
また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!
こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?
コメント