fix(server): scope Cache-Control no-cache to HTML pages, not static assets
The blanket `no-cache` from the earlier fix (#99) regressed the web-e2e cycle regression gate: it reloads /play-classic 240 times, and with no-cache the browser re-validated and recompiled the wasm on every load, blowing past the 30s bridge-ready timeout (green at #98, red from #99 onward). Scope no-cache to just the `include_str!` HTML routes (which change on every deploy and have no validators — the actual staleness source). The `/web` + `/assets` ServeDir keep their default Last-Modified caching, so repeated page loads reuse the downloaded/compiled wasm and the cycle gate is fast again. HTML freshness — the fix Rhys needed — is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -203,7 +203,12 @@ fn build_router_inner(state: AppState, rate_limit: bool) -> Router {
|
|||||||
// and the wasm-bindgen-generated `web/pkg/`). The HTML page is the
|
// and the wasm-bindgen-generated `web/pkg/`). The HTML page is the
|
||||||
// same regardless of `:id` — it reads the path from `location` in JS
|
// same regardless of `:id` — it reads the path from `location` in JS
|
||||||
// and fetches the replay JSON from `/api/replays/:id`.
|
// and fetches the replay JSON from `/api/replays/:id`.
|
||||||
let web = Router::new()
|
// HTML pages are `include_str!`'d into the binary and change on every
|
||||||
|
// deploy, so they get `Cache-Control: no-cache` (always revalidate). The
|
||||||
|
// `/web` + `/assets` static files keep ServeDir's default Last-Modified
|
||||||
|
// caching — applying no-cache to *those* too made the e2e cycle gate's 240
|
||||||
|
// page reloads recompile the wasm each time and time out.
|
||||||
|
let html_pages = Router::new()
|
||||||
.route(
|
.route(
|
||||||
"/",
|
"/",
|
||||||
get(|| async { Html(include_str!("../web/home.html")) }),
|
get(|| async { Html(include_str!("../web/home.html")) }),
|
||||||
@@ -233,6 +238,10 @@ fn build_router_inner(state: AppState, rate_limit: bool) -> Router {
|
|||||||
"/replays",
|
"/replays",
|
||||||
get(|| async { Html(include_str!("../web/replays.html")) }),
|
get(|| async { Html(include_str!("../web/replays.html")) }),
|
||||||
)
|
)
|
||||||
|
.layer(axum_middleware::from_fn(no_cache_headers));
|
||||||
|
|
||||||
|
let web = Router::new()
|
||||||
|
.merge(html_pages)
|
||||||
.nest_service("/web", ServeDir::new("solitaire_server/web"))
|
.nest_service("/web", ServeDir::new("solitaire_server/web"))
|
||||||
.nest_service("/assets", ServeDir::new("assets"))
|
.nest_service("/assets", ServeDir::new("assets"))
|
||||||
.layer(axum_middleware::from_fn(security_headers));
|
.layer(axum_middleware::from_fn(security_headers));
|
||||||
@@ -267,14 +276,18 @@ async fn security_headers(req: Request<axum::body::Body>, next: axum_middleware:
|
|||||||
HeaderValue::from_static("nosniff"),
|
HeaderValue::from_static("nosniff"),
|
||||||
);
|
);
|
||||||
headers.insert("X-Frame-Options", HeaderValue::from_static("DENY"));
|
headers.insert("X-Frame-Options", HeaderValue::from_static("DENY"));
|
||||||
// Force revalidation of the web assets. The HTML pages are compiled into
|
res
|
||||||
// the binary via `include_str!` and the wasm-bindgen output (canvas.js,
|
}
|
||||||
// canvas_bg.wasm, solitaire_wasm.*) keeps fixed filenames that change in
|
|
||||||
// place on every deploy. Without this, browsers heuristically cache them
|
/// Adds `Cache-Control: no-cache` so the browser always revalidates before
|
||||||
// and keep serving stale builds even after a hard reload. `no-cache` lets
|
/// using a cached copy. Scoped to the `include_str!` HTML pages (which change
|
||||||
// the browser keep a copy but revalidate first; ServeDir supplies
|
/// on every deploy and have no validators) — not the ServeDir static assets,
|
||||||
// Last-Modified/ETag so unchanged assets still return a cheap 304.
|
/// which keep normal Last-Modified caching so repeated page loads can reuse the
|
||||||
headers.insert("Cache-Control", HeaderValue::from_static("no-cache"));
|
/// already-downloaded/compiled wasm.
|
||||||
|
async fn no_cache_headers(req: Request<axum::body::Body>, next: axum_middleware::Next) -> Response {
|
||||||
|
let mut res = next.run(req).await;
|
||||||
|
res.headers_mut()
|
||||||
|
.insert("Cache-Control", HeaderValue::from_static("no-cache"));
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user