Cloudflare Workersで画像URLを一切変えずに軽量化する。

2021.04.07

ベルリンオフィスの小西です。

WordPressウェブサイトで、表示高速化のために画像を最適化したいケースがありました。

DNSとCDNですでにCloudflareを使っていたので、同じCloudlflareから提供されているWorkersを使って画像最適化処理を行ってみました。

Cloudflare Workersとは

Workersって何?って方はこちらのチュートリアルから → https://dev.classmethod.jp/articles/cloudflare-workers-tutorial/

ざっくりいうと、世界中のCloudflare CDNにサーバレスに実行可能なコードをデプロイできるサービスです。

やったこと

ユーザーからの画像リクエストに対し、Cloudflare Workersを挟んで、画像最適化を行って容量を削減します。

イメージは↓こんな感じ。CloudflareのCDNの各エッジで高速にスクリプトを実行してくれる点がメリットです。

最適化した内容は下記です。

  • 画像のリサイズ
  • 画像フォーマットの変更
  • 画像のクオリティ調整

Workersの設定

早速設定していきます。Cloudflareのアカウントは登録しておいてください。

1. スクリプトの実装

Cloudflareのアカウントホームの右サイドの右サイドバーから[Workers]ページに移動して、[Workersを作成]します。

スクリプト入力とテストが行えるコンソールが表示されますので、この画面で直接実装ができます。

下記、スクリプト本体です。リクエストを受け取り、画像に対してオプションを指定してFetchしています。

addEventListener("fetch", event => {
  if ( event.request.headers.get("via") ) {
    return fetch(event.request)
  } else{
    return event.respondWith(handleRequest(event.request))
  }
})

async function handleRequest(request) {
  const url = new URL(request.url)

  const imageRequest = new Request(url, {
    headers: request.headers,
  })

  const options = {  
    cf: {
      image: {
        fit: "scale-down",
        width: 1000,
        height: 800,
        quality: 60,
        format : "webp"
      }
    }
  }

  const response = await fetch(imageRequest, options)

  if (response.ok || response.status == 304) {
    return response;
  } else {
    return new Response(
      `Could not fetch the image — the server returned HTTP error ${response.status}`,
      {
        status: 400,
        headers: {
          'Cache-Control': 'no-cache',
        },
      },
    );
  }
}

上記で保存してデプロイします。まだこの状態では、Cloudflareで管理しているいずれの既存サイトにも影響を及ぼさないので大丈夫です。

後述しますがこのスクリプトをURLに紐付ける[ルーティング]が必要になります。

ちなみに画像リクエスト処理はコンソール上でテストができません(のでちょっと不安になるよね)。

スクリプトの説明

以下、ざっくり各部分の説明です。

if ( event.request.headers.get("via") ) {
    return fetch(event.request)
  } else{
    return event.respondWith(handleRequest(event.request))
  }

今回はリクエストされた画像URLに対して最適化処理済みの画像を返すので、リクエスト自体がループしないようにしています。


      image: {
        fit: "scale-down",
        width: 1000,
        height: 800,
        quality: 60,
        format : "webp"
      }

fetch()関数はオプションを取れます。上記は画像最適化のオプション指定部分です。

  • width / height : 幅1000pxもしくは高さ800px以上の画像を縮小リサイズ。整数のみ許容。
  • fit : "scale-down"は縮小のみ行う。"contain"にするとwidth, heightに合わせ拡大も可能。
  • quality : 画像の質を調整。1~100まで数字が低いほど質が下がり軽量になる。60は推奨最低値。
  • format : "webp"へ変換。ここは想定ブラウザの対応状況により必要に応じて検討してください。

const response = await fetch(imageRequest, options)

画像URLとoptionを fetch()で投げると画像処理をしてくれます。

公式のサンプルではURLパラメータに応じてサイズを変更する例を紹介していますが、今回はバックエンドCMSに触りたくないので、画像URLに対して一律で同様の最適化してしまいます。


2. ルーティング

[Workers]から[ルートを追加]を選択します。

Workersにおける[ルート]とは、スクリプトを適用するURLのルールのことです。

対象のURLにWorkersスクリプトを紐付けることで、Cloudflareがオリジンアクセスに仲介して処理を実行してくれます。(なのでWorkers的には、適用するURLルールは[ルート]で管理し、スクリプト本体には汎用性を持たせる設計がいいのかなと思います)

アスタリスク (*)が使えるので、例えばWordpressの画像フォルダにのみ先ほどのスクリプトを適用する場合、下記のような指定になるかと思います。

*.example.com/wp-content/uploads/*

[保存]を押すと、15秒以内にスクリプトが各エッジに展開されます。

これで画像をリクエストすると、画像が処理されて返ってくるはずです。

画像最適化オプションPolishとの併用

CloudflareにはPolishというオプションがあり、画像をよしなに最適化してくれます。

このPolish、Workersからもcfパラメータの中で指定することで利用できます。

  const options = {  
    cf: {
      polish: "lossy"
    }
  }

成功すると、ヘッダーに下記が確認できるはずです(画像クオリティが85に調整されている)。

cf-bgj: imgq:85  
cf-polished: qual=85, origFmt=jpeg, origSize=95005  
cf-cache-status: HIT

覚えておきたい点として、Polishと上記で紹介した画像リサイズは併用ができません

試してみたところ画像リサイズが優先されましたので、このPolishオプションが無視される点にご注意ください。

Workersによる画像リサイズの注意点

画像リサイズはビジネスプラン以上のオプションとなります。

通常プランで画像リサイズのoptionを投げてもエラーが出ずにそのまま画像が返ってきます。

僕はこれで1日ハマりました...

バックエンドに依存しない画像処理ができるよ

今回はURLをそのまま返しているので、CMS側で画像URLの書き換えが一切必要ありません。

またWorkersスクリプトを適用する対象URLは、[ルート]としてWorkersのダッシュボードで指定・管理していて、スクリプト本体と分離ができます。

これにより、バックエンドオリジンと疎結合化された処理や、オリジン画像の場所を秘匿した処理などが可能になります。

最後に

類似サービスに比べて実装から展開まで非常に高速かつ簡単に行える点がCloudflare Workersのメリットです。

クラスメソッドはCloudflareのパートナーとして、Cloudflare利用のお手伝いが可能です。

お問い合わせはお気軽にこちらから。

参考URL

https://developers.cloudflare.com/images/url-format

https://developers.cloudflare.com/images/resizing-with-workers