Cutting a 6.1 MB Worker bundle down to 2.3 MB
The 3 MB limit is not a suggestion, and your dependency graph does not care about your deadline.
Contents
Our deploy started failing with Script startup exceeded CPU time limit and, a week later, a hard size rejection. Here is the actual breakdown of where 3.8 MB went, in the order we found it, because the order matters — the first two took an afternoon and got us most of the way.
1. Look at the bundle before guessing (−1.9 MB)
npx wrangler deploy --dry-run --outdir dist then run the output through esbuild's metafile analyser. Two findings: a date library pulling in every locale, and an icon package where a single named import dragged in the full set because the package had no sideEffects: false. Swapping to per-icon imports was a one-line change worth 1.1 MB on its own.
2. Node polyfills you did not ask for (−780 KB)
One transitive dependency imported crypto, which pulled a browserify shim into the graph. The Workers runtime has crypto natively via nodejs_compat. Adding the compat flag and removing the shim was pure deletion.
3. Move the big, rare things off the hot path (−1.1 MB)
A syntax highlighter and a markdown pipeline were in the Worker because two routes needed them. They are now a separate Worker behind a service binding, called by those two routes only. The main Worker got smaller and its cold start went from 38ms to 11ms.
What I would do differently
Put the size check in CI on day one. We added a step that fails the build over 2.5 MB, which leaves headroom before the real limit. Every regression since has been caught in the PR that caused it, by the person who caused it, which is roughly a hundred times cheaper than finding it during a release.
Written by
Diego Alvarez
Platform and DevOps. I run our edge: Cloudflare Workers, KV, R2, and the CI that ships to them. Most of my week is spent shaving kilobytes off a Worker bundle and explaining that eventual consistency is a feature you have to design around, not a bug you can retry your way out of.
4 Comments
Sign in to join the discussion
The icon package one bites everybody. Per-icon imports plus
sideEffects: false, and then a lint rule banning the barrel import so it cannot come back in six months when someone copies an old file.