perf(http): add gzip compression and static asset cache headers

- Add CompressionLayer with gzip to the middleware stack
- Add Cache-Control: public, max-age=604800 (1 week) to all static
  asset routes (CSS, JS, favicons)
This commit is contained in:
Jon Seager 2026-02-06 18:05:09 +00:00
parent 494e0346bf
commit 4fb2e945a4
No known key found for this signature in database
3 changed files with 32 additions and 9 deletions

View file

@ -33,7 +33,7 @@ tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "json", "fmt"] } tracing-subscriber = { version = "0.3", features = ["env-filter", "json", "fmt"] }
tower = "0.4" tower = "0.4"
tower-cookies = "0.10" tower-cookies = "0.10"
tower-http = { version = "0.6", features = ["limit", "set-header", "trace"] } tower-http = { version = "0.6", features = ["compression-gzip", "limit", "set-header", "trace"] }
slug = "0.1.6" slug = "0.1.6"
url = "2" url = "2"
uuid = { version = "1", features = ["v4"] } uuid = { version = "1", features = ["v4"] }

View file

@ -43,49 +43,70 @@ async fn scan_redirect() -> Redirect {
async fn styles() -> impl IntoResponse { async fn styles() -> impl IntoResponse {
( (
[("content-type", "text/css; charset=utf-8")], [
("content-type", "text/css; charset=utf-8"),
("cache-control", "public, max-age=604800"),
],
include_str!("../../../../static/css/styles.css"), include_str!("../../../../static/css/styles.css"),
) )
} }
async fn webauthn_js() -> impl IntoResponse { async fn webauthn_js() -> impl IntoResponse {
( (
[("content-type", "application/javascript; charset=utf-8")], [
("content-type", "application/javascript; charset=utf-8"),
("cache-control", "public, max-age=604800"),
],
include_str!("../../../../static/js/webauthn.js"), include_str!("../../../../static/js/webauthn.js"),
) )
} }
async fn photo_capture_js() -> impl IntoResponse { async fn photo_capture_js() -> impl IntoResponse {
( (
[("content-type", "application/javascript; charset=utf-8")], [
("content-type", "application/javascript; charset=utf-8"),
("cache-control", "public, max-age=604800"),
],
include_str!("../../../../static/js/components/photo-capture.js"), include_str!("../../../../static/js/components/photo-capture.js"),
) )
} }
async fn searchable_select_js() -> impl IntoResponse { async fn searchable_select_js() -> impl IntoResponse {
( (
[("content-type", "application/javascript; charset=utf-8")], [
("content-type", "application/javascript; charset=utf-8"),
("cache-control", "public, max-age=604800"),
],
include_str!("../../../../static/js/components/searchable-select.js"), include_str!("../../../../static/js/components/searchable-select.js"),
) )
} }
async fn favicon() -> impl IntoResponse { async fn favicon() -> impl IntoResponse {
( (
[("content-type", "image/x-icon")], [
("content-type", "image/x-icon"),
("cache-control", "public, max-age=604800"),
],
include_bytes!("../../../../static/favicon.ico").as_ref(), include_bytes!("../../../../static/favicon.ico").as_ref(),
) )
} }
async fn favicon_light() -> impl IntoResponse { async fn favicon_light() -> impl IntoResponse {
( (
[("content-type", "image/svg+xml")], [
("content-type", "image/svg+xml"),
("cache-control", "public, max-age=604800"),
],
include_str!("../../../../static/favicon-light.svg"), include_str!("../../../../static/favicon-light.svg"),
) )
} }
async fn favicon_dark() -> impl IntoResponse { async fn favicon_dark() -> impl IntoResponse {
( (
[("content-type", "image/svg+xml")], [
("content-type", "image/svg+xml"),
("cache-control", "public, max-age=604800"),
],
include_str!("../../../../static/favicon-dark.svg"), include_str!("../../../../static/favicon-dark.svg"),
) )
} }

View file

@ -9,6 +9,7 @@ use axum::http::{HeaderValue, StatusCode};
use axum::response::Html; use axum::response::Html;
use tower::ServiceBuilder; use tower::ServiceBuilder;
use tower_cookies::CookieManagerLayer; use tower_cookies::CookieManagerLayer;
use tower_http::compression::CompressionLayer;
use tower_http::limit::RequestBodyLimitLayer; use tower_http::limit::RequestBodyLimitLayer;
use tower_http::set_header::SetResponseHeaderLayer; use tower_http::set_header::SetResponseHeaderLayer;
use tower_http::trace::{DefaultMakeSpan, DefaultOnResponse, TraceLayer}; use tower_http::trace::{DefaultMakeSpan, DefaultOnResponse, TraceLayer};
@ -62,7 +63,8 @@ pub fn app_router(state: AppState) -> axum::Router {
.layer(SetResponseHeaderLayer::overriding( .layer(SetResponseHeaderLayer::overriding(
axum::http::header::STRICT_TRANSPORT_SECURITY, axum::http::header::STRICT_TRANSPORT_SECURITY,
HeaderValue::from_static("max-age=63072000; includeSubDomains"), HeaderValue::from_static("max-age=63072000; includeSubDomains"),
)), ))
.layer(CompressionLayer::new().gzip(true)),
) )
.with_state(state) .with_state(state)
} }