build: serve the frontend with Caddy, and add a homepage perf harness #21
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "perf/homepage-first-paint"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The frontend container served everything uncompressed and uncacheable. This fixes that, and adds the harness that found it.
Confirmed against production before changing anything - the live entry bundle came back at
content-length: 187149even when asked forgzip, br, zstd, with nocontent-encoding, nocache-controland novary.Caddy replaces nginx
The old
nginx.confset no compression and no cache headers. Caddy adds zstd with a gzip fallback, and marks the fingerprinted/assets/*immutable while keepingindex.htmlonno-cache. Still rootless, still port 8080, TLS still terminated upstream. The config is also about a third the size.Verified on the built image: runs as uid 1000, serves
Content-Encoding: zstdwithVary: Accept-Encoding,Cache-Control: public, max-age=31536000, immutableon assets, and the SPA fallback intact.Two small things alongside it
index.htmlgets three lines of inline CSS naming the background. The stylesheet blocks the first paint, so without this the browser shows white until the bundle lands - on a dark app that reads as a flash of the wrong thing.main.tsxdrops thehydrateRootbranch left over from the prerender deleted in6a569e2. Nothing writes markup into#rootany more, so that path was dead.The harness
frontend/perf/homepage-loop.mjsproduced every number above. It serves the build over a local static server that gzips exactly like the Caddyfile, cold-loads it under pinned Slow-4G and 4x CPU throttling, and asserts byte and timing budgets. Byte counts are identical run to run; timings vary ~1%. Runs in about five seconds and needs no backend.Budgets are ratchets on today's measurements, not aspirations - lower them when something genuinely improves. Not wired into CI in this PR.
What this PR deliberately does not do
An earlier version of this branch added a boot shell: hand-written splash markup in
index.htmlplus two Vite plugins, painting at 348ms instead of 1200ms. It was measured against an uncompressed baseline, and once Caddy landed most of its value evaporated - it bought ~850ms of earlier pixels while making DOMContentLoaded and LCP marginally worse, in exchange for a duplicated splash that had to be kept in step withSplashScreen.tsx. Measured three ways and removed. Without it the app paints its own real splash at ~1.2s, which is the correct component doing its own job.Also left alone:
h1animates in over several seconds. Loading is no longer the constraint; the animation is. Its own problem./api/auth/refresh/returns - worth ~420ms, more against a real backend, but it changes behaviour for signed-in visitors.docs/adr/0002is stale - it records build-time pre-rendering for/, which6a569e2reversed.Verification
tscclean, 71 tests pass, both--mode productionand--mode e2ebuild, harness green.perf: kill the homepage blank screen, and compress what followsto build: serve the frontend with Caddy, and add a homepage perf harness