From 6d42e528b777be86e20226baae17bb2dfda6ea28 Mon Sep 17 00:00:00 2001 From: Xevion Date: Wed, 14 Jan 2026 15:57:35 -0600 Subject: [PATCH] feat: serve SvelteKit env.js for dynamic environment variables --- Dockerfile | 1 + src/assets.rs | 9 +++++++++ src/handlers/assets.rs | 16 ++++++++++++++++ src/routes.rs | 2 ++ 4 files changed, 28 insertions(+) diff --git a/Dockerfile b/Dockerfile index 95600a4..e834da8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -61,6 +61,7 @@ COPY migrations/ ./migrations/ # Copy frontend assets for embedding COPY --from=frontend /build/build/client ./web/build/client COPY --from=frontend /build/build/prerendered ./web/build/prerendered +COPY --from=frontend /build/build/env.js ./web/build/env.js # Build with real assets (use sqlx offline mode) ENV SQLX_OFFLINE=true diff --git a/src/assets.rs b/src/assets.rs index be39bc2..f1b975c 100644 --- a/src/assets.rs +++ b/src/assets.rs @@ -7,6 +7,7 @@ use include_dir::{Dir, include_dir}; static CLIENT_ASSETS: Dir = include_dir!("$CARGO_MANIFEST_DIR/web/build/client"); static ERROR_PAGES: Dir = include_dir!("$CARGO_MANIFEST_DIR/web/build/prerendered/errors"); static PRERENDERED_PAGES: Dir = include_dir!("$CARGO_MANIFEST_DIR/web/build/prerendered"); +static ENV_JS: &[u8] = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/web/build/env.js")); pub async fn serve_embedded_asset(uri: Uri) -> Response { let path = uri.path(); @@ -86,6 +87,14 @@ pub fn get_error_page(status_code: u16) -> Option<&'static [u8]> { ERROR_PAGES.get_file(&filename).map(|f| f.contents()) } +/// Get the embedded SvelteKit env.js file for dynamic public environment variables. +/// +/// SvelteKit generates this file when using `$env/dynamic/public` imports. +/// It must be served at `/_app/env.js` for prerendered pages to hydrate correctly. +pub fn get_env_js() -> &'static [u8] { + ENV_JS +} + /// Serve prerendered content by path, if it exists. /// /// Prerendered content is built by SvelteKit at compile time and embedded. diff --git a/src/handlers/assets.rs b/src/handlers/assets.rs index be4e864..f17f69f 100644 --- a/src/handlers/assets.rs +++ b/src/handlers/assets.rs @@ -8,6 +8,22 @@ use std::sync::Arc; use crate::{assets, proxy, state::AppState, utils}; +/// Serve SvelteKit's env.js for dynamic public environment variables. +/// Required for prerendered pages that use `$env/dynamic/public` imports. +pub async fn serve_env_js() -> impl IntoResponse { + let mut headers = HeaderMap::new(); + headers.insert( + axum::http::header::CONTENT_TYPE, + axum::http::HeaderValue::from_static("application/javascript; charset=utf-8"), + ); + headers.insert( + axum::http::header::CACHE_CONTROL, + axum::http::HeaderValue::from_static("public, max-age=3600"), + ); + + (StatusCode::OK, headers, assets::get_env_js()) +} + /// Serve PGP public key pub async fn serve_pgp_key() -> impl IntoResponse { if let Some(content) = assets::get_static_file("publickey.asc") { diff --git a/src/routes.rs b/src/routes.rs index fb2c1e0..769fee8 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -77,6 +77,8 @@ pub fn build_base_router() -> Router> { Router::new() .nest("/api", api_routes()) .route("/api/", any(api_root_404_handler)) + // Serve env.js explicitly before the wildcard (it's at build root, not in client/) + .route("/_app/env.js", get(handlers::serve_env_js)) .route( "/_app/{*path}", get(assets::serve_embedded_asset).head(assets::serve_embedded_asset),