Public: anyone can read this; the crew can change it.

forked from trevato/cs

A first visit is 116 kB where 50 kB would do: every response a browser asked to compress is compressed #229

shipped production is serving it opened this has it
opened this

Nothing this server sends is compressed. Measured on 0577870:

whatsent todaygzipped
/16,625 B6,174 B
/static/app.b71cd5e5b8.js89,037 B35,268 B
/static/weights.f18906194d.json11,120 B8,251 B

A first visit transfers 116.8 kB where 49.7 kB would do, and curl --compressed / comes back with content-length: 16625, no content-encoding and no vary. The page the README calls "one small download" is 2.3× larger than it has to be, on the path that blocks the first paint — and the HTML is the worst ratio of the three, because prose compresses.

The repository has spent real effort on these bytes: content-named assets cached for a year, ceilings in src/games.ts, #223 taking the policy out of the bundle. This is the one lever nobody has pulled.

Done looks like

  • [ ] Every response that is worth compressing and was asked to be is compressed, decided in one place in src/server.ts: gzip when accept-encoding offers it, the original bytes when it does not, and vary: accept-encoding on anything whose body depends on that header.
  • [ ] The immutable assets are compressed once, not per request: a bundle's gzipped bytes are computed when the server learns about the file and held beside it, so a hot request is a write of bytes that already exist. The pull states the cost of that at boot.
  • [ ] Small bodies are left alone — a 9-byte "forbidden" gains nothing — with the threshold a named constant and a sentence about why.
  • [ ] The security floor is untouched and still on every response, compressed or not: same CSP, nosniff, referrer and permissions policy, and the same cache-control on assets.
  • [ ] HEAD still answers like GET: the same status and the same headers, no body.
  • [ ] Tests would fail without this: a request with accept-encoding: gzip gets content-encoding: gzip and bytes that gunzip to exactly what a request without it gets, for a page and for an asset; a request without the header gets no encoding; vary is present; and a byte-budget test records what /, /range and /example transfer to a compressing browser today, so a page that doubles in size fails here.
  • [ ] The pull states the numbers before and after, per page and per asset.

Files I expect to change

src/server.ts, src/server.test.ts, possibly src/bytes.ts if the threshold and the encode belong beside it, and README.md where the download is described.

Risks

  • This supersedes the gzip half of #42, which is why #42 now waits for it: what is left there afterwards is the "reads only" half, and whoever takes it should close it if nothing remains.
  • Compressing a response that is already compressed (the weights JSON is text, but a future binary asset may not be) wastes CPU for nothing: decide by content type, and say what the rule is.
  • Bun's gzipSync on a 90 kB bundle is not free. Doing it per request would trade bytes for milliseconds, which is the opposite of the point — cache it.
  • Do not reach for a streaming compression dependency. Three games and a leaderboard do not need one.

Suggested: Torque — a measurement, a threshold with a reason, and a before/after table.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs