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:
- 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.
- 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:
- It bundled a frozen Photon snapshot that was painful to keep up to date.
- 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:
- Ingests PNG, JPG, BMP, ICO, and TIFF inputs.
- Encodes JPG, PNG, and WebP outputs (WebP is the default).
- Supports pipeline chaining for multiple consecutive actions.
- Edge caching on Cloudflare.
- Domain whitelist to prevent open-relay abuse.
- Graceful degradation: falls back to serving the original image on errors (errors are not cached).
Demo
Format Conversion
WebP

JPG

PNG

Resizing

Rotation

Cropping

Filters

Image Watermark

Text Watermark

Pipelining
Resize + Rotate + Text Watermark

Resize + Image 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:
