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.

The lesson that reframed the whole exercise: on this stack you do not choose your compressor, you choose your bytes. The edge compresses everything at its own fixed quality. The only lever the origin really owns is how many bytes go INTO the compressor.

Four roads to q11, all closed

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.

nav.js raw (committed)107,353
served, before (edge brotli)~33,000
served, now (minified + edge)20,376
the rejected .br twin (q11)17,403

Minification captured ~80% of what q11 twins would have given, at zero serving complexity: the edge just compresses a smaller file. The last ~3KB was not worth a second serving path.

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.

JS before · 1,105 bytes
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") { /* ... */ }
JS after · 622 bytes (44% smaller)
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"){/* ... */}
CSS before · 2,544 bytes
.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;
}
CSS after · 2,167 bytes (15% smaller)
.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:

habitatrawon the wire (brotli)verdict
homepage inline <style>30.4KB → 12.7KB~9.2KB → ~3.3KBprotected: View Source is the exhibit
worker-rendered chrome (xpChromeCss)9.4KB → 6.4KB84 bytes saved in page contextbrotli already did the job
the CSS strings inside nav.js−703 bytesGREW by 44 bytesrejected by the measurement itself
Habitat three is the fun one: minified CSS is higher-entropy per byte, and inside an already-minified JS file it compresses WORSE than the verbose original. Brotli voted no.
The moral: minification and compression are fighting over the same bytes. Minify-then-compress only wins where minification removes information, comments and long identifiers, which is why the JS build pays for itself. Where it only removes redundancy, whitespace and repeated patterns, brotli was going to erase that anyway. The one habitat where CSS minification would genuinely pay (~6KB a visit on the homepage) is exactly the one this site protects on purpose: the inline styles carry the comments that make View Source worth reading. That is the measured price of the rule, and it is paid knowingly.

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 outbrotli outtime
esbuild89,51224,42121.9ms
oxc-minify90,13124,3813.8ms
Totals across nav.js + notepad.js + lens.js + sw.js. Oxc is ~6x faster; the byte difference is 40 bytes of brotli, 0.16%, inside the noise.

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

 beforeaftermechanism
nav.js on the wire~33KB20.4KBminify at deploy, edge compresses less prose
sw.js on the wire3.8KB0.98KBit was 64% comments
repeat /lens, /writing, /readingfull re-rendercolo cache hitcaches.default, 200-only, short TTLs
readable sourceserved as written/<name>.src.js twinsthe build ships both
authoring workflowno buildno buildthe transform lives in deploy, not in writing
homepage bytes~31KB~31KBaccepted floor: it re-renders per visit, by design
The homepage line is the point of the whole page: per-visit randomization and a live counter are worth more here than 6KB of q11. Performance is a budget, and that is what this site chooses to spend it on.