fire-planner: SPA cache headers — index.html no-cache, hashed assets immutable
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

Browsers were caching the old index.html which still pointed at the
pre-Wave-2 bundle hash. Hashed assets under /assets/ stay cacheable
for a year (immutable), but index.html (and any SPA fallback) must
revalidate every request so a fresh deploy is visible immediately.
This commit is contained in:
Viktor Barzin 2026-05-10 17:01:11 +00:00
parent 727e0bed08
commit 2f95c891fa

View file

@ -166,17 +166,34 @@ class _SPAStaticFiles(StaticFiles):
`StaticFiles(html=True)` only serves index.html for *directories*,
not arbitrary not-found paths that's not enough for a SPA.
Cache policy: Vite hashes assets (e.g. ``index-XjyVM1-C.js``) so they
are immutable and can be cached forever. ``index.html`` references
those by hash if the browser caches a stale ``index.html`` it'll
keep loading the OLD bundle. Force ``index.html`` (and any non-hashed
response, including SPA fallbacks) to revalidate on every request.
"""
async def get_response(self, path: str, scope: Scope): # type: ignore[no-untyped-def]
try:
return await super().get_response(path, scope)
response = await super().get_response(path, scope)
except StarletteHTTPException as exc:
if exc.status_code == 404:
index = Path(self.directory) / "index.html" # type: ignore[arg-type]
if index.is_file():
return FileResponse(index)
return FileResponse(
index,
headers={"Cache-Control": "no-cache, must-revalidate"},
)
raise
# Hashed Vite assets live under /assets/ and contain an 8-char
# hash — safe to cache aggressively. index.html and anything
# else (logos, favicons) revalidate.
if path.startswith("assets/") and "-" in path:
response.headers["Cache-Control"] = "public, max-age=31536000, immutable"
else:
response.headers["Cache-Control"] = "no-cache, must-revalidate"
return response
# Mount the SPA last so it catches any path the API didn't claim.