Files
jared 096455944e fix(nginx): cache and compress the duckdb-wasm asset
The wasm engine is the single largest asset in the build (39,362,651 bytes) but
the immutable-cache location regex did not include `wasm`, so on the
Docker/nginx path it got no Cache-Control at all, and no gzip directive existed
anywhere -- nginx's default gzip_types is text/html only, so it shipped
uncompressed on every cold load.

Verified against nginx:alpine (1.31.3) with the real dist/ mounted:
`nginx -t` passes, and the wasm now returns Content-Type: application/wasm,
Content-Encoding: gzip, Cache-Control: public, max-age=31536000, immutable --
8,766,496 bytes on the wire instead of 39,362,651.

Dynamic gzip rather than gzip_static because the Vite build emits no
pre-compressed .gz files.
2026-08-11 10:37:27 -04:00

78 lines
2.7 KiB
Nginx Configuration File

# nginx config for CRDC Arrests Demo — static SPA + API reverse proxy with CORS.
# When served via Docker, this proxies /api/v1/ requests to the CRDC API server-side,
# adding Access-Control-Allow-Origin: * so browser-based fetch works without issues.
server {
listen 80;
server_name _;
# Proxy all /api/v1/ requests to the CRDC Arrest Rate API with CORS headers
location /api/v1/ {
proxy_pass https://crdc-api.civilytics.org/api/v1/;
proxy_set_header Host crdc-api.civilytics.org;
proxy_ssl_verify off;
# Add CORS headers so browser-based requests work
add_header Access-Control-Allow-Origin "*" always;
add_header Access-Control-Allow-Methods "GET, OPTIONS" always;
add_header Access-Control-Allow-Headers "*" always;
if ($request_method = 'OPTIONS') {
return 204;
}
}
# Also support a simple proxy endpoint for git-pages-style deployments
location /crdc-demo/proxy/ {
# Extract the target path from query string and forward to CRDC API
proxy_pass https://crdc-api.civilytics.org$arg_target;
proxy_set_header Host crdc-api.civilytics.org;
proxy_ssl_verify off;
add_header Access-Control-Allow-Origin "*" always;
add_header Access-Control-Allow-Methods "GET, OPTIONS" always;
}
# Serve static files from the Vite build output
root /usr/share/nginx/html/crdc-demo/; # Adjust if base is different
index index.html;
# Compress text assets and — most importantly — the duckdb-wasm engine,
# which is ~39MB uncompressed and ~8.8MB gzipped. Without this it ships
# uncompressed on every cold load. Dynamic gzip rather than gzip_static
# because the Vite build emits no pre-compressed .gz files.
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain
text/css
application/javascript
text/javascript
application/json
image/svg+xml
application/wasm;
location / {
try_files $uri $uri/ /index.html;
}
# Civilytics design tokens: immutable cache headers for static assets.
# `wasm` belongs here too — the duckdb engine is content-hashed by Vite and
# is by far the largest asset in the build, so it must not be re-fetched.
location ~* \.(js|css|wasm|png|jpg|jpeg|gif|svg|woff2|ttf)$ {
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable";
try_files $uri =404;
}
# Health check endpoint for container orchestration
location /healthz {
access_log off;
return 200 "ok";
add_header Content-Type text/plain;
}
}