Processing Images with Cloudflare Workers and WebAssembly

Background

I previously set up a free 10GB cloud storage bucket pairing Backblaze B2 with Cloudflare. I use it for daily file sharing and as an image host for my blog, uploaded via uPic. However, as an image bed for writing, it lacked dynamic resizing and cropping. I use Alibaba Cloud OSS image processing daily at work and got spoiled by on-the-fly transformations, so I wanted something similar for my personal setup.

Update: The free tier of Cloudflare Workers has a strict 10 ms CPU execution limit, which frequently triggered resource limit errors on larger images. The latest iteration uses Cloudflare Containers to process images reliably. I also adapted the project for Vercel Edge—see Processing Images with Vercel Edge.

Options Considered

I weighed two existing approaches:

  1. Proxying Vercel Image Optimization through Cloudflare: Request hops look like Cloudflare -> Vercel -> Cloudflare -> Backblaze. Latency and reliability were subpar, and the 1,000 free monthly transformations felt too restrictive.
  2. Using the public wsrv.nl service: Traffic hops through Cloudflare -> wsrv.nl -> Cloudflare -> Backblaze. It also means routing through a domain outside my control; keeping it on a custom domain would require an extra Worker proxy layer.

Neither felt right. Then, while working on an email worker, I noticed Cloudflare Workers supported WebAssembly (Wasm), which gave me the idea of running an image pipeline directly in WebAssembly at the edge.

My initial thought was to port sharp, my usual choice in Node.js. But sharp relies on native multi-threading, which Cloudflare Workers cannot run.

Digging around Rust crates, Photon stood out, and there was already a working community demo. It proved that Photon could compile to Wasm and run on Workers. However, that sample had two drawbacks:

  1. It bundled a frozen Photon snapshot that was painful to keep up to date.
  2. It only emitted PNGs, meaning resized JPEGs often ended up larger than the original files.

The Solution

Searching further into Photon and Workers, I drew inspiration from DenoFlare and jSquash. The final architecture combines official Photon (patched via patch-package), jSquash WebAssembly encoders, and a Cloudflare Worker router. (I wanted to support AVIF and JPEG XL as well, but hit the 1MB script size limit on free Workers and had to leave them out).

Supported features:

  1. Ingests PNG, JPG, BMP, ICO, and TIFF inputs.
  2. Encodes JPG, PNG, and WebP outputs (WebP is the default).
  3. Supports pipeline chaining for multiple consecutive actions.
  4. Edge caching on Cloudflare.
  5. Domain whitelist to prevent open-relay abuse.
  6. Graceful degradation: falls back to serving the original image on errors (errors are not cached).

Demo

Format Conversion

WebP

webp

JPG

jpg

PNG

png

Resizing

resize

Rotation

rotate

Cropping

crop

Filters

filter

Image Watermark

watermark

Text Watermark

draw_text

Pipelining

Resize + Rotate + Text Watermark

resize & rotate & draw_text

Resize + Image Watermark

resize & watermark

In theory, any operation supported by Photon works. Check the image URL parameters against the Photon documentation to test out different combinations.

Repository

The project is open source on GitHub:

ccbikai/cloudflare-worker-image - GitHub


Buy Me A Coffee

aria

© 2026 Aria

Instagram 𝕏 GitHub