server { listen 80; server_name _; root /usr/share/nginx/html; index index.html; # Compression for text assets. Brotli is a follow-up if it becomes a concern. gzip on; gzip_types text/css application/javascript application/json image/svg+xml; gzip_min_length 1024; gzip_vary on; # Hashed assets — long cache (Vite emits content-hashed filenames). location /assets/ { add_header Cache-Control "public, max-age=31536000, immutable"; try_files $uri =404; } # Runtime config — overridable in stage/prod via a Docker volume mount onto # /usr/share/nginx/html/config.json. Never cached so deploy-time overrides # take effect on the next page load. location = /config.json { add_header Cache-Control "no-cache, no-store, must-revalidate"; expires off; try_files $uri =404; } # index.html — never cache; the index references the hashed asset names. location = /index.html { add_header Cache-Control "no-cache, no-store, must-revalidate"; expires off; } # SPA routing fallback — every unknown path falls through to index.html so # the client-side router (TanStack Router) can resolve it. location / { try_files $uri $uri/ /index.html; } }