🖼️

Vercelから警告が来たので画像最適化について調べてみた 【Vercel & Cloudflare Images】

に公開

先月にDeevikというデスク環境の共有サービスをリリースしていたのですが、
こんな感じでサービスの性質上、画像を多く表示させています

Next.jsを使っているのでVercelにアプリをデプロイしており、画像最適化としてnext/imageコンポーネントを利用していました

そんな中、先日お世話になっているVercelさんよりこんなお便りが届きました

意訳: お前使い過ぎだから金払えよ

すみません月額$20かかるProプランはちょっと… という事で色々調べてみました

画像の最適化はサービス的に必須で、できれば既存の処理にあまり変更を加えない方法で良いやり方がないか模索していたところ、自分が調べた限りだと下記の2つで実現できそうでした

  • next/imageのキャッシュ保持期間を延ばす
  • CloudFlare Imageを利用したカスタムローダーを利用する

Deevikの構成

最近だとよく見かけそうな構成です

ホスティング 画像保存先 DB ドメイン
Vercel CloudFlare R2 Supabase CloudFlare

画像保管先はR2を利用しています。イグレス料金が無料だったりS3と互換性があったりと他にも色々とメリットがあります

またドメインはCloudFlareに委任をして、R2にカスタムドメインを割り当てると画像配信でキャッシュが効くようになったりします

next/imageとは

<Image />を使うだけで簡単に画像の最適化をしてくれる便利なコンポーネントです
詳しくはcatnoseさんの下記の記事にまとまっています

https://zenn.dev/catnose99/articles/883f7dbbe21632a5254e#fn-f2bb-1

Vercel上にホスティングしている場合、メールにあったように画像変換、キャッシュ読み込み、書き込みで利用枠を超えるとプランのアップデート or 従量課金が発生します

https://vercel.com/docs/image-optimization/limits-and-pricing

CloudFlare Imagesとは

Cloudflare Images は、Cloudflare が提供する「画像のストレージ・配信・変換」をワンストップで行うマネージドサービスです

CloudFlare Imagesを画像ストレージ、配信で利用するためには$5の有料プランに入る必要があります。しかし外部ストレージに保存されている画像の変換であれば無料プランでも月に5000枚まで無料で、それ以降は課金プラン+従量課金になるようです

またこちらは同じ変換パラメータ(例:width=100/thumbnail.jpg)を30日以内に再リクエストしても、重複して課金されないです

ただし画像変換を利用するためにはZONEの指定が必要になるため、CloudFlare上でドメインの登録、委任が必要になります
https://developers.cloudflare.com/images/pricing/

CloudFlare Images上に保存されている画像であれば画像変換のコストはかからないで、画像ストレージ、配信のコストのみ発生するようですね

サービス名のImages と名詞のImageが混ざって混乱する

方法① キャッシュの期間を延ばす

一番手軽な方法で単純に画像のキャッシュの期間を伸ばす方法です
https://nextjs.org/docs/app/api-reference/components/image#minimumcachettl

デフォルトだと1分しかキャッシュを保持しないみたいです
画像なのでMaxの1ヶ月で設定で問題なさそうです

方法② CloudFlare Imagesを利用したカスタムローダーを利用する

CloudFlare ImagesのTransformationsを利用したカスタムローダーを用いる事で、既存の<Image />コンポーネントをそのまま利用する事も可能です
https://developers.cloudflare.com/images/transform-images/integrate-with-frameworks/?utm_source=chatgpt.com

ただし注意点があって、CloudFlare Imageを利用したカスタムローダーを利用するにはCloudFlareのZone、つまりCloudFlareのProxyを経由しないといけないという点です

上記のリンクに記載があったカスタムローダーのreturn値は相対パスで返しています。これはCloudFlare上にデプロイされているアプリという事が前提になっています

const paramsString = params.join(",");
return `/cdn-cgi/image/${paramsString}/${normalizeSrc(src)}`;

なのでこれだとVercel上にデプロイしているアプリの場合に動かないです

自分は画像ストレージをR2用にサブドメインを設定していたので、
このドメインの絶対パスで返すようにしたところ無事に動くようになりました

料金比較

10万枚の画像を配信する想定で画像変換のコストのみで計算してみました

Vercel(Proプラン)

プラン料金: $20
無料枠: 10,000枚
追加料金: $0.05 - $0.0812 per 1K($0.05として計算)

$20 + (100,000 − 10,000) / 1,000 × $0.05 = $24.50

CloudFlare Images(外部オリジン)

プラン料金: $5
無料枠: 5,000枚
追加料金: $0.50 / 1,000 unique transformations

$5 + (100,000 − 5,000) / 1,000 × $0.5 = $52.50

概算ですが画像変換コストだけでいえばVercelの方が安いようです

結論

Vercelを利用している場合はVercelの画像最適化をそのまま利用した方が良さそうです
キャッシュの期間を伸ばしつつ、必要であればProプランにアップグレードする形が良さそうです

CloudFlare Pagesだと仕様上next/imageが利用できないという事象があるのですが、このような場合で画像最適化したい場合はカスタムローダーを利用するような形でしょうか

また今回は既存の処理になるべく変更を加えないという観点で見ていましたが、恐らくコスト的にはCloudFlare Imagesにアップロードしてそこから画像を配信する方法が安いと思います

最後に

画像を多く掲載するようなサイトの場合はちゃんと画像の配信方法を事前に調べるべきですね(当たり前)この記事がどなたかの役に立つ事があれば嬉しいです

また最初にも少し書いたように先日、個人開発でDeevikというデスク環境の共有サービスをリリースしました!もしご興味がある方は少し触ってみて頂けると嬉しいです🙏

https://deevik.dev/

Discussion