Workers では、カスタム URL スキームで画像を最適化できます。
Worker 内の fetch() サブリクエストでは、cf.image プロパティで画像最適化のパラメーターを設定できます。これは URL インターフェイス と同じ仕組みですが、画像リクエストごとにプログラムで制御できます。
URL ではなく画像バイトを直接扱う場合は、Images バインディング を使います。
Workers でできることの例は次のとおりです。
- カスタム URL スキームを使う。画像 URL にピクセル寸法を書く代わりに、
thumbnailやlargeなどのプリセット名を使います。 - 元画像の実際の場所を隠す。外部の S3 バケットや、サーバー上の非公開フォルダーに画像を置いても、その情報を URL に出しません。
- コンテンツネゴシエーションを実装する。端末やネットワークの状態に応じて、画像サイズ、フォーマット、品質を動的に変えられます。
リサイズ機能は、Worker 内の fetch() サブリクエスト の オプション から使います。fetch() 関数は、第 2 引数の {cf: {image: {…}}} オブジェクトでパラメーターを受け取ります。
Worker 内で fetch(request) により画像を取得していた箇所に、次の例のようにオプションを追加します。
fetch(imageURL, {
cf: {
image: {
fit: "scale-down",
width: 800,
height: 600,
},
},
});これらの型定義は、Workers TypeScript 定義ライブラリ ↗ でも利用できます。
cf.image は Worker をホストする任意のゾーンで使えます。*.workers.dev サブドメインも含みます。各変換は、その Worker を所有するアカウントに課金されます。
Cloudflare ダッシュボードの Workers セクションで、新しいスクリプトを作成します。Worker スクリプトのスコープは、/images/* や /assets/* など、アセット配信専用のパスにします。リサイズできるのは対応している画像フォーマットだけです。CSS や HTML など、それ以外のリソースをリサイズしようとするとエラーになります。
Worker が処理するパスは、元画像(未リサイズ)のパスと分けてください。画像リサイズ用 Worker が自分自身を呼び出すと、リクエストループになります。たとえば、画像は example.com/originals/ に置き、リサイズは example.com/thumbnails/* で /originals/ から取得します。元画像の保存先を Worker が処理する場合は、無限ループにならないようにする必要があります。
リサイズと最適化を行うには、Worker がオリジンサーバーから元の未リサイズ画像を取得できる必要があります。Worker が処理するパスと、サーバー上の画像パスが重なると、Worker が自分自身に画像を要求して無限ループになることがあります。
オリジンサーバーへ直接送るべきリクエストを判別する必要があります。Via ヘッダーに image-resizing という文字列がある場合、そのリクエストは別の Worker からのもので、オリジンサーバーへ向ける必要があります。
export default {
async fetch(request) {
// If this request is coming from image resizing worker,
// avoid causing an infinite loop by resizing it again:
if (/image-resizing/.test(request.headers.get("via"))) {
return fetch(request);
}
// Now you can safely use image resizing here
},
};Worker エディターのスクリプトプレビューは fetch() オプションを無視し、常に未リサイズの画像を取得します。画像変換の効果を確認するには、Worker スクリプトをデプロイし、エディターの外で使います。
wrangler dev の実行時、cf.image による変換は、精度の低いモックでローカルに適用されます。
リサイズ、回転、フォーマット、背景色など、一部のオプションだけが対象です。未対応のオプションは無視されます。
画像がリサイズできない場合(画像が存在しない、リサイズパラメーターが無効など)、レスポンスはエラーを示す HTTP ステータス(例: 400、404、502)になります。
デフォルトではエラーはブラウザーに転送されますが、扱い方は自分で決められます。たとえば、未リサイズの元画像へブラウザーをリダイレクトできます。
const response = await fetch(imageURL, options);
if (response.ok || response.redirected) {
// fetch() may respond with status 304
return response;
} else {
return Response.redirect(imageURL, 307);
}サーバー上の元画像が非常に大きい場合は、失敗した画像を表示しない方がよいことがあります。帯域やメモリを使いすぎたり、ページレイアウトが崩れたりするほど大きな画像へのフォールバックは避けてください。
失敗した画像をプレースホルダー画像に置き換えることもできます。
const response = await fetch(imageURL, options);
if (response.ok || response.redirected) {
return response;
} else {
// Change to a URL on your server
return fetch("https://img.example.com/blank-placeholder.png");
}https://example.com/image-resizing に Worker を設定 し、https://example.com/image-resizing?width=80&image=https://example.com/uploads/avatar1.jpg のような URL を処理する場合の例です。
/**
* Fetch and log a request
* @param {Request} request
*/
export default {
async fetch(request) {
// Parse request URL to get access to query string
let url = new URL(request.url);
// Cloudflare-specific options are in the cf object.
let options = { cf: { image: {} } };
// Copy parameters from query string to request options.
// You can implement various different parameters here.
if (url.searchParams.has("fit"))
options.cf.image.fit = url.searchParams.get("fit");
if (url.searchParams.has("width"))
options.cf.image.width = parseInt(url.searchParams.get("width"), 10);
if (url.searchParams.has("height"))
options.cf.image.height = parseInt(url.searchParams.get("height"), 10);
if (url.searchParams.has("quality"))
options.cf.image.quality = parseInt(url.searchParams.get("quality"), 10);
// Your Worker is responsible for automatic format negotiation. Check the Accept header.
const accept = request.headers.get("Accept");
if (/image\/avif/.test(accept)) {
options.cf.image.format = "avif";
} else if (/image\/webp/.test(accept)) {
options.cf.image.format = "webp";
}
// Get URL of the original (full size) image to resize.
// You could adjust the URL here, e.g., prefix it with a fixed address of your server,
// so that user-visible URLs are shorter and cleaner.
const imageURL = url.searchParams.get("image");
if (!imageURL)
return new Response('Missing "image" value', { status: 400 });
try {
// TODO: Customize validation logic
const { hostname, pathname } = new URL(imageURL);
// Optionally, only allow URLs with JPEG, PNG, GIF, or WebP file extensions
// @see https://developers.cloudflare.com/images/url-format#supported-formats-and-limitations
if (!/\.(jpe?g|png|gif|webp)$/i.test(pathname)) {
return new Response("Disallowed file extension", { status: 400 });
}
// Demo: Only accept "example.com" images
if (hostname !== "example.com") {
return new Response('Must use "example.com" source images', {
status: 403,
});
}
} catch (err) {
return new Response('Invalid "image" value', { status: 400 });
}
// Build a request that passes through request headers
const imageRequest = new Request(imageURL, {
headers: request.headers,
});
// Returning fetch() with resizing options will pass through response with the resized image.
return fetch(imageRequest, options);
},
};画像リサイズを試すときは、先にスクリプトをデプロイしてください。ダッシュボードのオンラインエディターでは、リサイズは有効になりません。
リサイズ済み画像は常にキャッシュされます。fetch サブリクエストのフルサイズ元画像 URL に対するキャッシュエントリの、追加バリアントとしてキャッシュされます。多数の Worker や多数の外部 URL を使っても問題ありません。リサイズ済み画像のキャッシュには影響せず、正しくキャッシュするために追加の作業は不要です。
複数の元 URL のキャッシュをまとめるために cacheKey の fetch オプションを使う場合は、cacheKey にリサイズオプションを含めないでください。キャッシュが細分化され、キャッシュ性能が低下します。cacheKey はフルサイズの元画像 URL だけを参照し、リサイズ済みバージョンは含めません。