summaryrefslogtreecommitdiffstats
path: root/packages
diff options
context:
space:
mode:
Diffstat (limited to 'packages')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/app.py24
1 files changed, 23 insertions, 1 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/app.py b/packages/meshbay-hub/src/meshbay_hub/app.py
index 668ae24..6240785 100644
--- a/packages/meshbay-hub/src/meshbay_hub/app.py
+++ b/packages/meshbay-hub/src/meshbay_hub/app.py
@@ -123,6 +123,28 @@ def create_app(cfg: HubConfig | None = None) -> FastAPI:
app.include_router(webapp_router)
from starlette.staticfiles import StaticFiles
- app.mount("/", StaticFiles(directory=STATIC_DIR), name="static")
+
+ class RevalidatingStatics(StaticFiles):
+ """Static assets that must be revalidated, never served blind from cache.
+
+ The SPA is a module graph that has to agree with itself: `app.js` imports
+ `i18n.js`, which imports a catalogue from `locales/`. Serving one of them
+ from cache while fetching another is not a stale page, it is a broken
+ one — a browser holding the previous `i18n.js` fails to link the new
+ `app.js` ("does not provide an export named 'initLocale'") before any
+ code runs, and the reverse pairing renders every string as its own key.
+
+ Starlette sends only `etag` and `last-modified`, and with no explicit
+ freshness a browser is entitled to guess one. `no-cache` does not
+ disable caching: it requires a conditional request, which the existing
+ ETag answers with a 304 and no body.
+ """
+
+ async def get_response(self, path, scope):
+ response = await super().get_response(path, scope)
+ response.headers.setdefault("Cache-Control", "no-cache")
+ return response
+
+ app.mount("/", RevalidatingStatics(directory=STATIC_DIR), name="static")
return app