diff options
Diffstat (limited to 'packages')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/app.py | 24 |
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 |