← Articles Indie Development · Tutorial

Deploying a 50MB WASM on Cloudflare Workers: A Troubleshooting Guide

A complete walkthrough of deploying a 50MB BaZi (八字) fortune-calculating WASM to Cloudflare Workers, and how Brotli compression solved the file size limit.

Cloudflare Workers has a hidden limit: individual static assets cannot exceed 25MB.

My BaZi (八字, Chinese Eight Characters) fortune-calculating WASM file is 50MB.

This is a problem that needs solving.

Analyzing the Problem

The WASM file is large for a simple reason: BaZi calculation requires a large amount of embedded calendar data (precise solar term timestamps, true solar time correction coefficients, etc.), and this data is compiled directly into the binary.

There are three possible solutions:

  1. Split the data: Strip the calendar data out of the WASM and load it on demand as separate JSON files.
  2. Compress on the wire: Keep the WASM unchanged, compress it with Brotli or Gzip for transfer, and let the browser decompress automatically.
  3. Switch platforms: Host the WASM on a storage service that supports larger files (such as R2).

Option 1 requires the most changes — it means modifying the Rust source code. Option 3 introduces additional complexity. I chose option 2.

The Impact of Brotli Compression

# Original size
ls -lh ganzhi.wasm
# -rwxr-xr-x  50M  ganzhi.wasm

# Brotli compression
brotli -q 11 ganzhi.wasm -o ganzhi.wasm.br
ls -lh ganzhi.wasm.br
# -rwxr-xr-x  12M  ganzhi.wasm.br

The compression ratio reaches 76% — 12MB is comfortably within Cloudflare’s 25MB limit.

Worker Configuration

The key is that the Worker needs to set the correct response headers for .wasm.br files, telling the browser this is a Brotli-compressed WASM file:

if (pathname.endsWith('.wasm.br')) {
  const response = await env.ASSETS.fetch(request);
  const headers = new Headers(response.headers);
  headers.set('Content-Type', 'application/wasm');
  headers.set('Content-Encoding', 'br');
  headers.set('Cache-Control', 'public, max-age=86400');
  return new Response(response.body, { status: response.status, headers });
}

When the browser receives Content-Encoding: br, it automatically decompresses the payload and hands the result to WebAssembly.instantiate() as a WASM module. The entire process is completely transparent to the frontend code.

Results

The deployment succeeded, and the BaZi calculation tool is running normally. Users download a 12MB compressed file, the browser decompresses it into a 50MB WASM on the receiving end, and the whole process takes roughly 5–15 seconds (depending on network speed).

Sometimes the most elegant solution isn’t a refactor — it’s adding the right response header in the right place.


The related tool is now live; you can try the full calculation experience at the “八字命盤” (BaZi Chart) entry point above.