server { listen 80; server_name _; root /usr/share/nginx/html; index index.html; # nginx only sees its own container-internal port (80) and has no idea # docker-compose maps that to some other host port — an absolute redirect # (e.g. nginx's own trailing-slash directory redirect) would silently # drop that external port from the Location header. A relative Location # lets the browser resolve it against whatever host:port it's already on. absolute_redirect off; # The stock /etc/nginx/mime.types (included by the base image's own # nginx.conf) already maps .wasm/.woff2/.html correctly. .webmanifest # isn't in it, so it's set per-location below rather than via a # server-level types{} block, which would replace that whole map instead # of extending it. .data has no meaningful type of its own and correctly # falls through to the base config's default_type (application/octet-stream). location = /manifest.webmanifest { default_type application/manifest+json; add_header Cache-Control "no-cache"; } location = /sw.js { add_header Cache-Control "no-cache"; } # Large, effectively-immutable engine binaries — keep gzip off so nginx # can still serve byte-range/If-Range requests (on-the-fly gzip disables # range serving), and cache them aggressively. location /vendor/vpinball-wasm/ { gzip off; add_header Cache-Control "public, max-age=31536000, immutable"; } # error_page preserves the 404 status while serving the prerendered # not-found page's content — a bare `try_files ... /404.html;` fallback # would serve that same content with a 200, since try_files treats an # existing fallback file as a normal successful response. error_page 404 /404.html; location = /404.html { internal; } location / { try_files $uri $uri/ $uri.html =404; } }