server { listen 80; server_name _; root /usr/share/nginx/html; index index.html; # ---- gzip ---- gzip on; gzip_vary on; gzip_min_length 1024; gzip_proxied any; gzip_comp_level 6; gzip_types text/plain text/css text/javascript application/javascript application/json application/xml application/rss+xml image/svg+xml font/ttf font/otf font/woff font/woff2 application/vnd.ms-fontobject; # ---- Hashed static assets: cache hard ---- # ^~ so this wins over the regex block below for Vite's fingerprinted files location ^~ /assets/ { expires 1y; add_header Cache-Control "public, immutable"; try_files $uri =404; } # ---- Other static files by extension ---- location ~* \.(?:js|css|png|jpe?g|gif|webp|avif|svg|ico|woff2?|ttf|otf|eot|mp4|webm)$ { expires 30d; add_header Cache-Control "public"; try_files $uri =404; } # ---- SPA fallback: unknown routes -> index.html (React Router) ---- location / { try_files $uri $uri/ /index.html; } # index.html must never be cached, so new deploys are picked up location = /index.html { add_header Cache-Control "no-cache, no-store, must-revalidate"; expires -1; } # Don't log noise / don't cache the favicon aggressively is fine as-is location = /favicon.ico { log_not_found off; access_log off; expires 30d; } }