Next.js 应用上线,Nginx 是绕不开的一层:反向代理、HTTPS、缓存、压缩都在这里。本文给出一套经过生产验证的完整配置,按"反代 → 压缩 → 缓存 → HTTPS"四步走。

一、基础反向代理:把流量交给 Node

Next.js(next start)监听 127.0.0.1:3000,Nginx 占 80/443 对外:

server {
    listen 80;
    server_name www.example.cn;

    # 真实客户端 IP 透传(日志与风控依赖)
    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;

    # 静态构建产物:Nginx 直接接手,不经过 Node
    location /_next/static/ {
        proxy_pass http://127.0.0.1:3000;
        add_header Cache-Control "public, max-age=31536000, immutable";
    }

    # 其余全部反代(页面 / API / 图片优化)
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;      # WebSocket 兼容
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 60s;
    }
}

两个高频事故点:

  • 忘了 X-Forwarded-Proto:HTTPS 下 Next 误判协议,生成的回调/分享链接变成 http,微信场景直接被拦
  • 没配 Upgrade 头:后续加 WebSocket(实时通知、协同)时连接握手失败

二、Gzip/Brotli:文本资源先砍 70%

gzip on;
gzip_comp_level 6;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript
           application/xml image/svg+xml;
gzip_vary on;      # 告知 CDN 缓存压缩与未压缩两个版本

有条件上 Brotli(ngx_brotli 模块),同样级别压缩率比 gzip 再高 15%~20%:

brotli on;
brotli_comp_level 5;
brotli_types text/plain text/css application/json application/javascript;

注意:/_next/static/ 里的资源在构建时已预压缩(.gz/br 后缀文件),配合 gzip_static on; 可以直接吐压缩文件,零 CPU 消耗。

三、缓存策略:immutable 的正确打开方式

Next.js 构建产物文件名带内容哈希,内容变则文件名变,因此可以放心"永久缓存":

资源策略
/_next/static/*max-age=31536000, immutable
/_next/image?*max-age=86400(图片优化结果,内容可能变)
HTML 页面no-cache(协商缓存,保证内容新鲜)
API 响应默认 no-store,列表类接口按业务加短缓存

再加一层 proxy cache 给高频页面(首页、新闻列表)减负:

proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=pagecache:10m inactive=60s;

location /news {
    proxy_cache pagecache;
    proxy_cache_valid 200 60s;     # 60 秒微缓存,高峰期效果显著
    proxy_cache_use_stale error timeout updating;
    proxy_pass http://127.0.0.1:3000;
}

四、HTTPS:Certbot 三条命令

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d www.example.cn -d example.cn
sudo certbot renew --dry-run   # 验证自动续期(certbot 自带定时任务)

HTTP 强制跳转 HTTPS,并补上安全响应头:

server {
    listen 80;
    server_name www.example.cn example.cn;
    return 301 https://www.example.cn$request_uri;
}

server {
    listen 443 ssl http2;
    server_name www.example.cn;
    ssl_certificate     /etc/letsencrypt/live/www.example.cn/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/www.example.cn/privkey.pem;

    add_header Strict-Transport-Security "max-age=31536000" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}

五、进程守护:systemd 替代 PM2 的写法

# /etc/systemd/system/aiyun-web.service
[Unit]
Description=Aiyun Website (Next.js)
After=network.target mysql.service

[Service]
Type=simple
User=www
WorkingDirectory=/srv/www/aiyun
Environment=NODE_ENV=production
Environment=PORT=3000
ExecStart=/usr/bin/node node_modules/.bin/next start
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now aiyun-web

六、上线检查清单

  1. curl -I 验证 gzip/br 生效、静态资源 immutable 头正确
  2. HTTPS 全链路(含图片域名),HTTP 301 到 HTTPS
  3. 故意重启 Node 进程,确认 Nginx 502 页面友好且 systemd 自动拉起
  4. nginx -t 语法检查后再 reload,绝不直接 restart 生产
  5. 日志切割(logrotate)配置到位,避免磁盘写爆

小结

Nginx 层的价值在于:把静态、压缩、缓存、TLS这些"每请求都要做但 Node 不擅长的事"接管走,让 Node 只做业务。配置不复杂,但每一行都值得写进团队的标准模板。