2020-04-13に投稿

Nowで運用している画像配信サーバーにリサイズ機能を追加した

Cloud Runに引っ越したことで重いページとそうでないページがはっきりしてきた(以前は全てのページが重くて逆に気にならなかった)のだが、その際にLighthouseで調べているとPerformanceのスコアが非常に悪く、画像の読み込みにめちゃくちゃ時間がかかっていることに気づいた。

トップページの記事ランキングなどにはサムネイルを表示していたりするのだが、アップロードされた画像をそのまま表示していた。中には横幅2000とか3000以上の画像もあったりする。そのためそういう画像がいくつも同じページ内にあると当然読み込みに時間がかかるし、Wifiじゃない状況でスマホで見たりすると通信容量の消費も激しくなるだろうし全然ユーザーフレンドリーじゃない。

ということで丁度モチベーション切れだったこともあり、せっかく気になってきたので対応してみることにした。

画像配信サーバーの改造

アップロードされた画像は全てZeitのNowで運用している画像配信サーバーで配信している。

NowのエッジキャッシュでCloud Storage節約サーバー作成

ということで、これを改造してリサイズに対応できればよいのではないかと思った。具体的には通常の画像のURLの後ろに ?mw=700&mh=300 のように、パラメータを指定した場合だけリサイズするようにする形。

リサイズに使用したパッケージ

リサイズと言えばImageMagick(現在はgm?)かなと思ったが、一応調べてみたところsharpというパッケージも非常に人気があるらしかったのでそちらを使ってみることにした。

lovell/sharp: High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images. Uses the libvips library.

一番シンプルな形だとこんな感じでいける。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開発者

Crieitの開発者です。 Webエンジニアです(在宅)。大体10年ちょい。 記事でわかりにくいところがあればDMで質問していただくか、案件発注してください。 業務依頼、同業種の方からのコンタクトなどお気軽にご連絡ください。 業務経験有:PHP, MySQL, Laravel, React, Flutter, Vue.js, Node, RoR 趣味:Elixir, Phoenix, Nuxt, Express, GCP, AWS等色々 PHPフレームワークちいたんの作者

Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

有料記事を販売できるようになりました!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?

コメント