Appearance
Cache and invalidation
This page documents the cache-control story end-to-end.
Today: no-cache everywhere
Every asset response from MatrixHttpAssetEndpointActor carries:
cache-control: no-cachePer index.ts:163:
typescript
const headers: Record<string, string> = {
'content-type': MIME_TYPES[ext] ?? 'application/octet-stream',
'content-length': String(stat.size),
'cache-control': 'no-cache',
};no-cache does not mean "do not cache." It means "the cache may store this, but must revalidate before serving from cache." A 304 Not Modified flow is supported; the browser sends If-None-Match and the server responds 304 if the ETag matches.
Note: The current asset endpoint does not implement ETag/Last-Modified validation.
If-None-Matchrequests succeed at the cache check level (the server doesn't refuse) but always return the full body. Adding proper conditional-request handling is part of the cache-tightening target state.
Why no-cache today
Pre-launch development is fast-moving. Tightening caching introduces a class of "I deployed a fix but the browser still shows the old version" bugs. no-cache is the safe default while the deployment cadence is high.
Target state
Two-tier caching:
| Asset class | Cache-control |
|---|---|
Hashed asset (*.abc123.js, *.def456.css) | public, max-age=31536000, immutable |
index.html, manifest.json | no-cache (or short max-age) |
API responses (/api/*) | no-store |
Hashed assets are content-addressed by name; their content cannot change without their URL changing, so an aggressive immutable directive is correct. Browsers and CDNs cache them indefinitely.
index.html references the hashed asset URLs. When a new build ships, index.html references new asset URLs; the browser revalidates index.html (short cache), gets the new version, and fetches the new hashed assets.
Invalidation
In the target-state model:
- Deploying a new package version produces a new build with new asset hashes.
- The new
index.htmlreferences the new hashed assets. - Browsers fetching
index.htmlrevalidate (short cache or no-cache). - They get the new HTML, then fetch the new asset URLs (cache miss, fresh fetch).
- Old asset URLs in old caches are never refetched and eventually evict.
This is the standard "cache-busting via filename" pattern that every modern build tool produces.
CDN considerations
When a CDN sits between Caddy and the browser (HiveCast does not currently deploy with a CDN, but a future architecture could), the CDN respects cache-control directives. Hashed assets get aggressive CDN caching; index.html does not. CDN purges become unnecessary because nothing long-cached needs invalidating.
What doesn't help today
- Adding
Cache-Control: max-age=0headers everywhere. Same asno-cachefor our purposes; doesn't solve the underlying revalidation issue. - Setting
must-revalidate. Combined withno-cache, redundant. - Versioning asset URLs by query string (
/index.js?v=2). Hashed asset names are strictly better — the browser cache can store both versions side-by-side during transitions.
Browser cache when WebSocket disconnects
NATS WebSocket disconnect doesn't affect HTTP cache. Each is independent. The browser may notice the WS disconnect, attempt to reconnect, and during that period continue using cached HTML/JS. State refresh happens through NATS, not HTTP cache invalidation.
Asset endpoint relay limit
Unrelated to caching but worth mentioning here: assets >10 MB cannot be relayed (the MAX_RELAY_BODY_BYTES constant). Caching a 10 MB asset is fine once it has been fetched, but the gateway's relay layer is the bottleneck for the initial fetch.
See also
Source:
projects/matrix-3/packages/system-gateway-http/src/index.ts:160-167.