diff --git a/assets/static/logo/logo.png b/assets/static/logo/logo.png new file mode 100644 index 0000000..3d86446 Binary files /dev/null and b/assets/static/logo/logo.png differ diff --git a/assets/static/logo/original.png b/assets/static/logo/original.png new file mode 100644 index 0000000..b44df0c Binary files /dev/null and b/assets/static/logo/original.png differ diff --git a/src/app.rs b/src/app.rs index 6d1364a..8b523cc 100644 --- a/src/app.rs +++ b/src/app.rs @@ -65,6 +65,10 @@ impl Hooks for App { async fn after_routes(router: axum::Router, ctx: &AppContext) -> Result { let casbin = crate::shared::rbac::layer().await?; Ok(router + // Stamp a long-lived Cache-Control on /static/* responses only. + .layer(axum::middleware::from_fn( + crate::shared::cache::static_cache, + )) .layer(casbin) .layer(axum::middleware::from_fn_with_state( ctx.clone(), diff --git a/src/shared/cache.rs b/src/shared/cache.rs new file mode 100644 index 0000000..fa8266d --- /dev/null +++ b/src/shared/cache.rs @@ -0,0 +1,32 @@ +//! Long-lived `Cache-Control` for static assets. +//! +//! Loco's static-file middleware serves `/static/*` without any caching headers, +//! so browsers refetch the CSS/JS/images on every visit (Lighthouse: "Use +//! efficient cache lifetimes"). This middleware stamps a one-year immutable +//! cache on those responses only — dynamic HTML pages are left untouched so +//! carts, CSRF tokens and prices stay fresh. +//! +//! Safe because the cache-busting lives in the URL: `app.css?v=…` is query- +//! versioned and the vendored libs (`htmx-1.9.12`, `alpinejs-3.14.9`) carry +//! their version in the filename. The handful of unversioned images under +//! `/static/img` (logo, store hero) must be renamed (or query-busted) to force +//! a refresh. + +use axum::{ + extract::Request, + http::header::{HeaderValue, CACHE_CONTROL}, + middleware::Next, + response::Response, +}; + +const ONE_YEAR_IMMUTABLE: HeaderValue = + HeaderValue::from_static("public, max-age=31536000, immutable"); + +pub async fn static_cache(req: Request, next: Next) -> Response { + let is_static = req.uri().path().starts_with("/static/"); + let mut res = next.run(req).await; + if is_static { + res.headers_mut().insert(CACHE_CONTROL, ONE_YEAR_IMMUTABLE); + } + res +} diff --git a/src/shared/mod.rs b/src/shared/mod.rs index 3d35aeb..9b4c5ee 100644 --- a/src/shared/mod.rs +++ b/src/shared/mod.rs @@ -1,5 +1,6 @@ //! Cross-cutting helpers used across feature slices. +pub mod cache; pub mod csrf; pub mod currency; pub mod guard;