Skip to content

Serve hashed client chunks as immutable so warm page loads stop revalidating all 186 - #6684

Merged
atomantic merged 2 commits into
mainfrom
cos/app-improve-portos-default-performance-mttjv4la/agent-90a874f2
Sep 9, 2026
Merged

Serve hashed client chunks as immutable so warm page loads stop revalidating all 186#6684
atomantic merged 2 commits into
mainfrom
cos/app-improve-portos-default-performance-mttjv4la/agent-90a874f2

Conversation

@atomantic

Copy link
Copy Markdown
Owner

Summary

  • The built UI was served with express.static defaults, so every content-hashed chunk under /assets/ went out as Cache-Control: public, max-age=0, which a browser treats as stale on arrival. Measured with a request-counting proxy in front of a spare-port server and Chrome with service workers blocked (the default plain-HTTP tailnet posture, where sw.js cannot register): a warm dashboard load re-sent all 186 chunks as conditional GETs and got 186 304s back before the app could start.
  • New mountClientDist in services/assetMounts.js serves /assets/** as public, max-age=31536000, immutable and keeps the revalidate-per-load default for the rest of dist/ (sw.js, manifest.json, fonts/, sky/, hdri/ are client/public/ files copied under stable names whose bytes do change). index: false stays on both tiers so / still reaches the build-id-stamped SPA fallback, and a chunk the current build no longer ships still 404s, which the stale-chunk reload relies on.
  • Same measurement after the change: 0 asset requests on a warm load or reload; a first visit to /settings fetches only its own 14 new chunks (was 42).
Phase (no service worker) Before: /assets/ requests After
Cold first load 186 (all 200) 186 (all 200)
Warm navigation to / 186 (all 304) 0
Warm reload 186 (all 304) 0
Warm first visit to /settings 42 (14 × 200, 28 × 304) 14 (all 200)

Test plan

  • server/services/assetMounts.test.js: hashed chunk carries the immutable header; sw.js and manifest.json keep public, max-age=0 with an ETag; / reaches the SPA fallback, not the raw index.html; a stale chunk 404s instead of getting the SPA index.
  • scripts/dev-proxy-drift.test.js (reads server/index.js as data) plus every other suite that reads server/index.js: 703 tests green.
  • Booted the branch on a spare port and confirmed with curl: /assets/index-*.jspublic, max-age=31536000, immutable; /sw.jspublic, max-age=0; /no-cache; /assets/index-STALEHASH.js → 404 JSON.
  • Browser measurement above, before and after, on the same built dist/.

…idating all 186

The built UI was served with express.static defaults, so every content-hashed
chunk under /assets/ went out with `Cache-Control: public, max-age=0`. A browser
treats that as stale on arrival: measured against a spare-port server with a
request-counting proxy and Chrome with service workers blocked (the default
plain-HTTP tailnet posture, where no service worker can register), a warm
dashboard load re-sent all 186 chunks as conditional GETs and got 186 304s back
before the app could start. Over HTTP/1.1 at six connections per host that is
~31 sequential round-trip waves of pure revalidation on every reload.

Vite renames a chunk whenever its bytes change and the `no-cache` index.html is
what points at the current set, so `/assets/**` is safe to serve with
`immutable, max-age=1y`. `mountClientDist` in services/assetMounts.js does that
as a first tier and keeps the revalidate-per-load default for the rest of
dist/ — `sw.js`, `manifest.json`, `fonts/`, `sky/`, `hdri/` are client/public
files copied under stable names whose bytes do change. `index: false` stays on
both so `/` still reaches the build-id-stamped SPA fallback, and a chunk the
current build no longer ships still misses both tiers and 404s, which the
stale-chunk reload depends on.

Same measurement after the change: 0 asset requests on a warm load or reload,
and a warm navigation to a not-yet-visited route fetches only its own new
chunks (14 instead of 42).
@atomantic
atomantic merged commit c69ca4c into main Sep 9, 2026
13 of 24 checks passed
@atomantic
atomantic deleted the cos/app-improve-portos-default-performance-mttjv4la/agent-90a874f2 branch September 9, 2026 04:08
@atomantic
atomantic restored the cos/app-improve-portos-default-performance-mttjv4la/agent-90a874f2 branch September 9, 2026 04:09
@atomantic
atomantic deleted the cos/app-improve-portos-default-performance-mttjv4la/agent-90a874f2 branch September 9, 2026 04:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant