Skip to content

Compression in the browser

Compression runs in the browser, before a byte crosses the network. A raw phone photo typically shrinks 5–10×, which is bandwidth the user does not spend and storage you do not buy.

It runs in a worker on OffscreenCanvas, so the main thread never blocks — scrolling stays smooth while a dozen photos encode.

Measured with bun build --minify --splitting, which is what a real bundler does. (Without --splitting the number is 1.38 MB and it is a lie — everything lands in one chunk whether or not you ever reach it.)

gzipped who pays
the compressor, eager 4,363 B everyone
compressorjs, lazy 4,994 B Safari < 16.4, or a failed decode
heic2any, lazy 341,352 B each HEIC — and only if the engine cannot decode it natively
hash-wasm 7,223 B everyone. Tree-shakes cleanly; there is no alternative, since WebCrypto has no incremental digest

That last column is the whole design. Only 4 KB is unconditional; the expensive pieces load when something actually needs them.

Engines that can decode HEIC do it in about 136 ms. The WASM fallback takes roughly 1,700 ms — and costs 341 KB to fetch first. So the native path is tried first, and the fallback only runs where it must.

That is a 12× difference on the exact file type phones produce by default, so it is worth confirming on a real device rather than in a simulator.

canvas.convertToBlob({ type: "image/webp" }) is allowed to ignore you. WebKit 26.5 returns image/png when asked for WebP; Chromium does not. Nothing throws, and the result still looks like an image.

Measured on an 11.9 MP photo: PNG 21.8 MB vs JPEG 3.8 MB — a 5.7× penalty, and 24× against a well-tuned WebP. Worse, a file labelled .webp that is actually a PNG breaks anything downstream that trusts the extension.

The rule this produced, and it generalises well beyond this SDK:

Non-image inputs — video, PDF — skip compression entirely and upload raw. An image the engine cannot decode also uploads raw rather than failing the upload: the original arriving intact beats a clever error.