server { listen 8080; server_name _; # Docker embedded DNS, re-queried every 10s so rebuilt containers are picked up. resolver 127.0.0.11 valid=10s; root /usr/share/nginx/html; index index.html; location /api/ { # Resolve `api` at request time: a rebuilt api container gets a new # IP, and a startup-time-only DNS answer would pin nginx to it (502s). set $api_upstream http://api:8080; proxy_pass $api_upstream; proxy_http_version 1.1; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } location = /health { set $health_upstream http://api:8080; proxy_pass $health_upstream/health; proxy_set_header Host $http_host; } # 带内容哈希的构建产物:可以长期缓存(换版本必然换文件名);旧哈希文件已被新构建替换 # ⇒ 明确 404,不要回退成 index.html(否则浏览器把 HTML 当 JS 执行,报一堆语法错误)。 location /assets/ { expires 1y; add_header Cache-Control "public, max-age=31536000, immutable"; # 当前构建里没有再找**上一版构建**的资源:CDN/浏览器可能还持有旧 index.html, # 它引用的旧哈希文件若直接 404,页面就会空白或"裸奔"(只有文字没样式)。 # 旧目录由部署脚本在每次重建前落盘(deploy/legacy-assets,只读挂载)。 try_files $uri @legacy; } location @legacy { root /usr/share/nginx/legacy; try_files $uri =404; } # ⚠️ index.html(含 SPA 深链回退)**必须每次回源校验**:它引用的是带哈希的 JS 文件名, # 一旦被 CDN/浏览器缓存住,页面就一直停在旧构建上(看着像"新功能没上线")。 # 2026-10-14 实测被腾讯云 EdgeOne 缓存卡住过一次:源站已是新包,公网入口仍发旧包。 location = /index.html { # 只发一条 Cache-Control(`expires -1` 会再发一条,CDN 读到哪条说不准)。 add_header Cache-Control "no-cache, must-revalidate" always; } location / { try_files $uri $uri/ /index.html; add_header Cache-Control "no-cache, must-revalidate" always; } }