Skip to content

Instantly share code, notes, and snippets.

@jakeisnt
Last active January 17, 2025 14:15
Show Gist options
  • Save jakeisnt/1cae0f2f21c77d9dc5910f670e255fa3 to your computer and use it in GitHub Desktop.
Save jakeisnt/1cae0f2f21c77d9dc5910f670e255fa3 to your computer and use it in GitHub Desktop.
Cloudflare worker: leverage cloudflare image resizing
/**
* - Run `npm run dev` in your terminal to start a development server
* - Open a browser tab at http://localhost:8787/ to see your worker in action
* - Run `npm run deploy` to publish your worker
*
* Bind resources to your worker in `wrangler.toml`. After adding bindings, a type definition for the
* `Env` object can be regenerated with `npm run cf-typegen`.
*
* Learn more at https://developers.cloudflare.com/workers/
*/
const B2_URL = 'YOUR_BACKBLAZE_IMAGE_URL';
/**
* Parse query parameters and return an object with width, height, quality, and format.
* If no query parameters are present, return null.
*
* @param params - The URLSearchParams object containing query parameters.
* @param request - The Request object.
* @returns An object with width, height, quality, and format, or null if no query parameters are present.
*/
const parseQueryParams = (params: URLSearchParams, request: Request) => {
const width = Number(params.get('w') || params.get('width')) || undefined;
const height = Number(params.get('h') || params.get('height')) || undefined;
const quality = Number(params.get('q') || params.get('quality')) || undefined;
const format = request.headers.get('Accept')?.includes('image/webp') ? ('webp' as const) : ('jpeg' as const);
if (!width && !height && !quality) {
return null;
}
return { width, height, quality, format };
};
/**
* Return a 404 response with a message.
*
* @param message - The message to return in the response.
* @returns A 404 response with the message.
*/
const notFound = (message: string = '! img') =>
new Response(message, {
status: 404,
headers: {
'Content-Type': 'text/plain',
'Cache-Control': 'public, max-age=3600',
},
});
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const url = new URL(request.url);
const path = url.pathname;
const params = url.searchParams;
if (path === '/') {
return new Response('OK', { status: 200 });
}
const queryConfig = parseQueryParams(params, request);
const b2URL = `${B2_URL}${path}`;
const originResponse = await fetch(b2URL, {
cf: {
image: {
...queryConfig,
fit: 'scale-down',
},
},
});
if (originResponse.ok) {
return new Response(originResponse.body, {
status: 200,
headers: {
'Cache-Control': 'public, max-age=31536000',
'Content-Type': originResponse.headers.get('content-type') || 'image/jpeg',
},
});
}
// Return 404 if the origin image isn't found
return notFound(`${originResponse.statusText}: ${originResponse.status}\n 'failed to fetch original image'`);
},
} satisfies ExportedHandler<Env>;
@jakeisnt
Copy link
Author

Instructions:

  1. Create a new subdomain record for the images on Cloudflare
  2. Add this code to a cloudflare worker, replacing the B2_URL const with your preferred image bucket
  3. Connect the worker to the route

NOTE: This doesn't seem to be compatible with local Wrangler debugging but functions correctly when hosted on their production system. Setting this up for local development involves round-tripping to 'cdn-cgi/image' with URL parameters that correspond to image resizing commands, enabling the route-based Cloudflare image transformation API to do so.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment