From 2f95c891fa2ccf26fd11d5b828a07b0f459f0cb8 Mon Sep 17 00:00:00 2001 From: Viktor Barzin Date: Sun, 10 May 2026 17:01:11 +0000 Subject: [PATCH] =?UTF-8?q?fire-planner:=20SPA=20cache=20headers=20?= =?UTF-8?q?=E2=80=94=20index.html=20no-cache,=20hashed=20assets=20immutabl?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- fire_planner/app.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/fire_planner/app.py b/fire_planner/app.py index 17190a0..4f8a769 100644 --- a/fire_planner/app.py +++ b/fire_planner/app.py @@ -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.