diff --git a/.gitignore b/.gitignore
index d49920ab..63769679 100644
--- a/.gitignore
+++ b/.gitignore
@@ -68,6 +68,12 @@ web/dist/*
web/dist/js/*
!web/dist/js/runtime-bundle.min.js
+# ── Pre-compressed static assets (S3) ──────────────────────────────
+# Generated by `node scripts/precompress-static.mjs` during build.
+# Runtime gzip in services/gzipped_static_files.py handles the missing case,
+# so dev mode without a build step still works.
+web/**/*.gz
+
# ── IDE & OS ───────────────────────────────────────────────────────
.DS_Store
Thumbs.db
diff --git a/app.py b/app.py
index bf9df46b..69a97320 100644
--- a/app.py
+++ b/app.py
@@ -30,6 +30,7 @@
from fastapi.staticfiles import StaticFiles
from config import ConfigManager
+from services.gzipped_static_files import GzippedStaticFiles
# Logging
from services.structured_logger import configure_logging
@@ -293,9 +294,9 @@ async def dispatch(self, request: Request, call_next):
# Mount locales FIRST (more specific path takes precedence)
if os.path.isdir(locales_dir):
- main_app.mount("/static/locales", StaticFiles(directory=locales_dir), name="locales")
+ main_app.mount("/static/locales", GzippedStaticFiles(directory=locales_dir), name="locales")
# Then mount web/ for remaining static files
- main_app.mount("/static", StaticFiles(directory=web_dir), name="static")
+ main_app.mount("/static", GzippedStaticFiles(directory=web_dir), name="static")
# Generated chapter images
images_dir = os.path.join(base_dir, "output", "images")
diff --git a/package.json b/package.json
index bd74fa52..7dc35720 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,8 @@
"type": "module",
"scripts": {
"dev": "vite",
- "build": "tsc -p tsconfig.build.json && node scripts/build-runtime-bundle.mjs",
+ "build": "tsc -p tsconfig.build.json && node scripts/build-runtime-bundle.mjs && node scripts/precompress-static.mjs",
+ "build:precompress": "node scripts/precompress-static.mjs",
"build:bundle": "node scripts/build-runtime-bundle.mjs",
"build:css": "tailwindcss -i web/css/main.css -o web/css/main.built.css --minify",
"build:vite": "vite build",
diff --git a/scripts/build-runtime-bundle.mjs b/scripts/build-runtime-bundle.mjs
index 335fa954..3a586124 100644
--- a/scripts/build-runtime-bundle.mjs
+++ b/scripts/build-runtime-bundle.mjs
@@ -1,14 +1,17 @@
#!/usr/bin/env node
/**
- * build-runtime-bundle.mjs — concat + minify the 13 runtime
-
-
-
+
+