# Serves the built Angular bundle and reverse-proxies the API, so the browser # sees a single origin and never makes a cross-origin request. server { listen 8080; server_name _; root /usr/share/nginx/html; index index.html; # Client uploads are capped server-side too; this stops oversized bodies # from being buffered all the way to the API first. client_max_body_size 6m; gzip on; gzip_types text/css application/javascript application/json image/svg+xml; gzip_min_length 1024; # Do not advertise the exact nginx version. server_tokens off; include /etc/nginx/snippets/security-headers.conf; # Hashed build assets are immutable, so they can be cached hard. location ~* \.(?:js|css|woff2?|ttf|eot|svg|png|jpg|jpeg|gif|webp|ico)$ { include /etc/nginx/snippets/security-headers.conf; # add_header alone, not `expires`: using both emits two Cache-Control # headers with overlapping directives. add_header Cache-Control "public, max-age=31536000, immutable" always; try_files $uri =404; } location /api/ { proxy_pass http://api:8080; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # Uploaded box art, served straight off the API's volume. # # `^~` matters: without it, a regex location wins over a prefix location, so # /uploads//.webp would fall into the static-asset block above and # 404 against nginx's own filesystem instead of being proxied. location ^~ /uploads/ { include /etc/nginx/snippets/security-headers.conf; proxy_pass http://api:8080; proxy_http_version 1.1; proxy_set_header Host $host; add_header Cache-Control "public, max-age=2592000" always; } location /health { proxy_pass http://api:8080/health; } # index.html must never be cached, or clients keep booting old bundles. # Declared before `location /` so the internal rewrite below lands here. location = /index.html { include /etc/nginx/snippets/security-headers.conf; add_header Cache-Control "no-store, no-cache, must-revalidate" always; } # Angular owns routing: any unknown path returns index.html so a deep link # or a refresh on /games/12 does not 404. location / { try_files $uri $uri/ /index.html; } }