Bytes on the wire
This site was no-build on principle: every page inline, every byte committed the way it ships. This is the story of the first build step that earned its place anyway, the brotli rabbit hole that led there, and the cache layer that finally made a header the site had been sending for months actually do something. Every number below was measured on this site, most of them twice.
Where compression actually happens
The naive model says "Cloudflare serves brotli, so the bytes are as small as
brotli gets." The real model has tiers. Brotli has quality levels 0 to 11:
level 11 is the slow, offline, squeeze-everything setting, and no CDN runs it
on the fly, because compressing at q11 costs more time than it saves.
Cloudflare's edge compresses dynamic and pass-through responses at roughly
gzip quality. Measured on the homepage: the edge served
31,360 bytes as brotli and 31,188
as gzip, nearly identical. A local brotli q11 of the same document:
25,099. That gap, roughly 12 to 16% on every text asset,
is the room between what the edge does in microseconds and what q11 does
offline.
Four roads to q11, all closed
- Native brotli in the Worker. There is none. Workers'
CompressionStreamspeaks gzip and deflate only. Dead end by platform design. - wasm brotli at q11, per request. The homepage re-renders per visit (random photo grid + live counter), so it would compress on the hot path: multi-ms of CPU per request plus a wasm blob in the bundle to save ~6KB. Rejected on arithmetic.
- Worker-side gzip level 9. Measured a local gzip-9 of the
homepage at
31,204bytes vs the edge's31,207. Three bytes. The edge's gzip is already at the ceiling; only brotli has headroom, and see above. Rejected as a no-op. - Precompressed .br twins. The interesting one. Pages
ignored uploaded
.brfiles (tested, it served the plain twin). Workers CAN do it: fetch the twin from assets and return it withcontent-encoding: brandencodeBody: "manual", which tells the runtime the body is already compressed. But that means the asset goes back through the worker (undoing the zero-invocation edge-direct routing), plus Accept-Encoding negotiation and Vary correctness, to beat the edge's own brotli by ~3KB on a file that was about to get much smaller anyway. Rejected, narrowly, with the next section as the reason.
The build step that earned its place
The shell scripts are the one external asset class: nav.js
(the whole XP desktop: taskbar, draggable windows, Run palette) rides on every
page. It is written the way this site writes everything: essay-grade comments,
because the comments are the documentation and half the voice. That costs
nothing in git and ~23KB of the 107KB file on the wire. Compression does not
forgive prose: the served brotli was ~33KB.
So the split, and the compromise that finally justified a build: the
authoring stays buildless, the serving gets minified. One script
(build.mjs, one dependency, esbuild) stages a copy of the
site, minifies exactly four files, and deploys from the copy. The repo never
changes; a bare wrangler deploy still works and just
ships the readable versions.
The rest of the four: sw.js turned out to be
64% comments and dropped from 3.8KB to under 1KB on the wire;
lens.js 6.3 to 5.0KB; notepad.js
4.0 to 2.4KB. index.html and every garage page are
deliberately NOT touched: View Source on this site is supposed to read like
source.
Keeping View Source honest
Minified JS is hostile to the one person a personal site should welcome: the
curious reader. So the build deploys every readable original alongside as
/<name>.src.js, and the minified file opens with a
pointer:
/*! minified at deploy - readable source: /nav.src.js */
Two details I did not expect to enjoy this much. The build has
tripwires: it fails the deploy if minification ever eats the
service worker's version string or the marker the route oracle greps for, so
"the transform broke something silently" is structurally impossible. And
Cloudflare's asset storage is content-addressed, so on the first minified
deploy the four .src.js twins uploaded as "already
uploaded": their bytes were identical to the shells the previous deploy had
shipped. The readable originals were already in storage, just under a
different name.
What about the CSS?
Minifying CSS is the same idea as minifying JS, and worth seeing side by side,
because the two languages do not shrink the same way. Both snippets below are real
code from this site (the scroll-remember helper from nav.js,
and this very page's Luna close-button gel), run through the same minifier. Byte
counts are for the full block; the excerpts are trimmed to fit.
function rememberScroll(sc) {
var key = "axp-scroll:" + location.pathname;
var save = function () {
try { sessionStorage.setItem(key,
String(sc.scrollTop)); } catch (e) {}
};
var nav = (performance.getEntriesByType &&
performance.getEntriesByType("navigation")[0]) || {};
if (nav.type === "reload") { /* ... */ }function rememberScroll(e){var t="axp-scroll:"
+location.pathname,r=function(){try{
sessionStorage.setItem(t,String(e.scrollTop))}
catch{}},s=performance.getEntriesByType&&
performance.getEntriesByType("navigation")[0]
||{};if(s.type==="reload"){/* ... */}.title-bar .controls .close {
position: relative; box-sizing: border-box;
width: 21px; height: 21px; padding: 0;
border: 1px solid #6696eb; border-radius: 3px;
background-image: linear-gradient(180deg,
#e8795f 0%, #e45f40 30%, #e45d3d 52%,
#e2552a 80%, #ae3110 100%);
transition: filter 60ms ease-out;
}.title-bar .controls .close{position:relative;
box-sizing:border-box;width:21px;height:21px;
padding:0;border:1px solid #6696eb;
border-radius:3px;background-image:
linear-gradient(180deg,#e8795f,#e45f40 30%,
#e45d3d 52%,#e2552a 80%,#ae3110);
transition:filter 60ms ease-out}The gap is the whole story. JS shrinks 44% because a minifier can rename
things: every local variable collapses to one letter, information genuinely leaves
the file. CSS shrinks 15% because nothing in it is renamable: selectors and
properties are the page's public API, so all a CSS minifier can remove is
whitespace, comments, and micro-slack (note it dropped the redundant
0% and 100% gradient stops). And
whitespace is exactly the thing brotli already compresses to almost nothing.
So the real question is never "does the file get smaller" but "does the WIRE get smaller after brotli." This site's CSS lives in three habitats, and I measured all three:
| habitat | raw | on the wire (brotli) | verdict |
|---|---|---|---|
homepage inline <style> | 30.4KB → 12.7KB | ~9.2KB → ~3.3KB | protected: View Source is the exhibit |
| worker-rendered chrome (xpChromeCss) | 9.4KB → 6.4KB | 84 bytes saved in page context | brotli already did the job |
| the CSS strings inside nav.js | −703 bytes | GREW by 44 bytes | rejected by the measurement itself |
Sidebar: Rolldown + Oxc, the faster knife
The new Rust toolchain (Rolldown the bundler, Oxc the parser/minifier under it) is genuinely faster than esbuild. Tested on this site's four real shells, same machine, same brotli after:
| raw out | brotli out | time | |
|---|---|---|---|
| esbuild | 89,512 | 24,421 | 21.9ms |
| oxc-minify | 90,131 | 24,381 | 3.8ms |
Verdict: right tool, wrong scale. This build minifies four files once per deploy in CI; the entire esbuild pass costs 22 milliseconds, so a 6x speedup saves 18ms nobody is waiting on, and the output bytes are a wash. esbuild also happens to be the engine wrangler already uses to bundle the worker, so it is one mental model instead of two. Rolldown itself solves a problem this site refuses to have (bundling). If the build ever grows into thousands of files, Oxc is the obvious switch; today it would be a dependency swap in search of a benefit.
Making s-maxage true
The second shipment in this batch fixed a quieter embarrassment. Worker-rendered
pages here sent s-maxage=300, and the edge ignored it:
Cloudflare does not automatically cache worker output, so
cf-cache-status read none and the
header was decorative. Every visit to /lens re-assembled a
byte-identical shell; every visit to /writing re-fetched
every post's text just to print character counts.
The fix is the Workers Cache API, caches.default: on miss,
render and store; on hit, serve from the colo. Three rules carried over from
this site's scar tissue: only status 200 is ever stored (this
site once served homepage HTML at thumbnail URLs with a year-long cache header,
and does not repeat mistakes it has written down), TTLs stay short and honest
because deploys do NOT flush this cache, and every response says which path it
took in an x-edge-cache header. The reading page also
dropped its no-store (it was conservatism, not
necessity), and the manual refresh hook evicts the edge copy too, so a bust
still busts.
Watch it happen
This fetches /lens and /reading
twice each and prints the x-edge-cache header from
your nearest Cloudflare colo. A fresh colo shows miss
then hit; a warm one may say
hit twice. Your browser hitting production, nothing
mocked.
The honest scorecard
| before | after | mechanism | |
|---|---|---|---|
| nav.js on the wire | ~33KB | 20.4KB | minify at deploy, edge compresses less prose |
| sw.js on the wire | 3.8KB | 0.98KB | it was 64% comments |
| repeat /lens, /writing, /reading | full re-render | colo cache hit | caches.default, 200-only, short TTLs |
| readable source | served as written | /<name>.src.js twins | the build ships both |
| authoring workflow | no build | no build | the transform lives in deploy, not in writing |
| homepage bytes | ~31KB | ~31KB | accepted floor: it re-renders per visit, by design |