Next.js standalone 上线排障:Nginx、静态资源和来源校验
复盘 Boker 的 Next.js standalone 部署:Nginx 代理、静态资源缺失和后台来源校验。
Boker 在本地能构建、能跑测试,并不代表通过服务器 IP 打开就能正常写文章。2026 年 9 月的首次实际使用里,先遇到页面没有样式,随后又遇到后台保存和正文插图提示“请求来源无效”。这篇按排查顺序记录原因、修复和验证方法。
先弄清谁在提供什么
Boker 不是可以直接交给 Nginx 托管的静态目录。公开文章需要服务端查询数据库,后台需要登录会话、写入 API 和受保护的媒体读取。当前服务器的实际链路是:
浏览器 -> Nginx :80 -> Next.js standalone 127.0.0.1:3000
|-> PostgreSQL
|-> 持久化上传目录
systemd 管理 Node.js 进程,Nginx 转发全部路径,包括 /_next/static/... 和 /api/media/...。代理必须保留浏览器使用的 Host,并设置 X-Forwarded-Proto,因为应用要知道外部请求究竟从哪个协议和主机进入。
仓库还提供了另一套更规范的发布入口:npm run release:package 从已提交代码生成源码包和 SHA-256 校验文件;安装脚本在独立版本目录安装依赖、构建、迁移,切换 /srv/boker/current 并检查健康状态。当前验收过的服务器尚未改用这套目录布局,而是从项目目录运行 standalone。写部署记录时把两者分开,避免把 /srv/boker/current 的命令直接照搬到当前服务器。
故障一:HTML 有了,CSS 没有
浏览器能打开页面却没有样式时,先看 /_next/static/...css 是否返回 200,而不是马上改 CSS。问题出在 standalone 产物的准备:next build 生成 .next/standalone/server.js,但运行目录还需要公开资源与 .next/static。缺少后者时,HTML 仍能返回,样式和客户端脚本却会 404。
仓库中的 deploy/prepare-standalone.sh 会把 public/ 和 .next/static/ 复制到 standalone 对应位置,并清除构建时可能被复制进去的环境文件。构建后执行该脚本,再启动 standalone,两个 IP 入口的登录页和 CSS 都应返回 200。这个故障提示我们:验证首页状态码不足以证明前端资源完整,至少还要请求一次页面实际引用的 CSS 和 JS。
故障二:本站后台被判为“来源无效”
最初的写入保护把浏览器发来的 Origin 与 SITE_URL 固定比较。SITE_URL 是站点的规范地址;实际使用时,站长可能从内网 IP,也可能从 Tailnet IP 打开同一个 Nginx。浏览器会按当前地址发送 Origin,于是本来同源的保存文章、图片上传和登录请求也被拒绝。
修复分两层。第一层用 Nginx 转发的 Host 和 X-Forwarded-Proto 还原当前外部来源,并要求请求的 Origin 与它完全一致。第二层为生产环境 HTTP 写入设置显式白名单 ADMIN_HTTP_ORIGINS,只允许指定的两个 IP;公开域名的 HTTP 写入和其他 IP 仍返回 403。SITE_URL 继续负责 canonical 和 sitemap,不再承担“所有后台请求从哪里来”的判断。
还有一个容易漏掉的点:旧登录代码只要处于生产环境就给会话 Cookie 加 Secure。浏览器通过 HTTP IP 访问时,这种 Cookie 无法正常用于登录。现在 HTTPS 入口使用 Secure Cookie,明确允许的 HTTP IP 入口按 HTTP 设置。HTTP 本身不提供应用层传输加密,因此这个入口应限定在可信网络;如果将来开放公网域名后台,应配置 HTTPS。
我们怎样确认修复真的到了线上
排查时先用临时生产进程测试,再检查 Nginx 的真实入口。空正文或未登录请求的状态码可以帮助定位流程走到了哪一步:
| 请求 | 预期状态 | 说明 |
|---|---|---|
两个 IP 的 /admin/login 和页面 CSS |
200 | 页面与静态资源可读 |
| 两个 IP 的匿名文章写入、匿名图片上传 | 401 | 来源校验通过,接下来要求登录 |
| 两个 IP 的空字段登录请求 | 422 | 来源校验通过,字段验证拦下请求 |
| 不在白名单内的 HTTP 主机写入 | 403 | 来源策略拒绝 |
这些状态码只能证明对应关卡,不能代替真实账号的操作。最终由站长在浏览器中验证了登录、退出、上传图片、发布、编辑和删除。
给下一次部署留下的清单
当前目录式运行方式的关键顺序是:确认数据库和上传目录备份,安装依赖与迁移,npm run build,bash deploy/prepare-standalone.sh,重启 systemd,再检查 Nginx 与浏览器流程。完整的版本包安装、校验和回退步骤以 deploy/README.md 为准。
每次变更域名、代理配置或访问协议,都要重新核对 Host、X-Forwarded-Proto、ADMIN_HTTP_ORIGINS、Cookie 属性、CSS/JS 请求和媒体权限。代码可以切回旧版本,数据库迁移却不会自动回滚;发布前的数据库及上传目录备份不能省。