返回博客
5 分钟

本地跑得好好的,上线后我修了一整晚

从 localhost 到公网,出问题的通常不是代码。八件真实发生过的事,按我踩到的顺序写下来。

上线部署踩坑
本文目录

晚上十一点半,我把项目推上去了。

https://项目名.pages.dev 打开,首页正常。我截图发到群里,配了两个字:上线。

三分钟后朋友回:「点第二个按钮白屏。」

那一晚我基本没睡,之后几天又陆续补了几件。真正让我难受的是:这八件事里,没有一件是产品逻辑的 bug。

一、.env 是代码的一部分,只是你看不见

本地有个 .env.local,从我建项目那天就在。部署的时候我在面板里填了数据库地址和 API key,觉得填齐了。

漏了 NEXT_PUBLIC_API_URL。而代码里那行是这么写的:

const api = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000';

这个兜底在本地看着挺贴心,上线就是灾难。前端把请求打到了 http://localhost:3000/api/comment,控制台一行红字:

POST http://localhost:3000/api/comment net::ERR_CONNECTION_REFUSED

在我机器上它一直是对的,因为我机器上真有个服务跑在 3000。

修法不复杂:仓库里放一个 .env.example,把用到的变量名全列出来,值留空。部署完对着它一条条核对。别信记性——本地能跑,恰恰是因为环境变量早就躺在那里,你从来没为它做对过任何事。

二、服务器上的磁盘是临时的

这个坑我在别人的文章里读到过,还是踩了。

评论区的图片存在 ./uploads。我传了一张,刷新,在。看起来没问题。

第二天早上平台做了一次例行重启。所有图片 404。

容器、函数这类运行环境,本地文件系统只在实例活着的时候存在。重启、扩缩容、重新部署,都会换一块干净的新盘。要留住东西就得用对象存储(Cloudflare R2、S3 这一类的都行),或者挂个卷。

判断方法很土但有效:问自己一句「如果这台机器今晚被销毁重建,会丢什么」。

三、www 和裸域是两个不同的站点

域名在 Cloudflare 上买好,DNS 里加了 www 和 @ 两条记录,都指向同一个 Pages 项目。都能打开,我以为配完了。

然后登录态开始诡异:在 patet.xyz 登录,跳到 www.patet.xyz 就退出了。

因为 cookie 的 Domain 写的是 www.patet.xyz。在浏览器眼里这是两个站,不是同一个。

顺带一提,搜索引擎也是这么看的,权重白分一份。做法是挑一个当正主,在 Cloudflare 加一条 Redirect Rule 把另一个 301 过去,对外只留一个地址。这一步五分钟,建议在发链接之前做。

四、应用要监听 0.0.0.0

这条我卡了快一个钟头,因为它给的报错完全不指向问题。

现象是 502,nginx 日志里躺着:

connect() failed (111: Connection refused) while connecting to upstream

但我在应用容器里 curl localhost:3000 明明是有返回的。

问题出在这行:

app.listen(3000, '127.0.0.1');

它只接受来自本机回环地址的连接。我的 nginx 跑在另一个容器里,从它的角度看,127.0.0.1 是它自己,不是我的应用。改成 app.listen(3000)(或者显式写 '0.0.0.0')就好了。

那次之后,排 502 我固定成两步:先在服务器上 curl 一次应用端口。通,是反代的问题;不通,是应用没起来或者端口不对。能省掉一大半瞎猜。

五、开了 HTTPS,混合内容才开始报错

橙云一开,证书自动就有了,页面变成 https,很爽。

然后控制台出现这个:

Mixed Content: The page at 'https://patet.xyz/' was loaded over HTTPS,
but requested an insecure resource 'http://cdn.example.com/lib.js'.
This request has been blocked.

原因是我在代码里写死了一处 http://。浏览器不会帮你降级,它直接拦掉,而且拦得很安静——只有一个按钮不工作,其他都好。

全局搜一遍 http://,图片、脚本、接口地址,该换的换。另外 Cloudflare 的 SSL 模式别选 Flexible,那意味着 Cloudflare 到你源站那段还是明文,看着加密了,其实没有。用 Full (strict),具体步骤在接入 Cloudflare 里。

六、密钥提交过 git,删文件是没用的

这条不是我那一晚踩的,是我朋友。

他把 .env 提交上去了,过了两天发现,git rm,又 commit 了一次,以为处理完了。

没用。历史里还在,在 GitHub 上照样翻得出来。他花两个小时轮换了数据库密码、支付平台密钥和云存储的 key,还给用户发了一封邮件。

规矩很简单:.gitignore 从第一天就写好;密钥只放平台的环境变量里;真提交了,就当它已经泄露,先轮换,其他都往后排。日常该做的加固在运维安全 那一篇。

七、上线之后,没人知道它挂了

这条最容易被跳过,因为它是「没有发生的事故」。

我的服务凌晨两点挂过一次。早上九点我自己打开网站才发现。中间七个小时,没有一条通知。

加一个 uptime 监控,很多服务有免费档;再配一个错误上报。不用搞复杂,能在挂掉五分钟内让手机响一下,就够了。

八、两周后,搜索引擎里搜不到自己

网站能用了,但这一条是我后来才补上的。

我在 Google 里搜自己的域名,什么都没有。三个原因,按我踩中的概率排:

  1. robots.txt 里带着模板自带的 Disallow: /——我是从某个 starter 抄的,压根没看;
  2. <head> 里还留着 noindex;
  3. 没有 sitemap,也没去 Search Console 提交。

三样都改完,第二天就收录了。细节都在 SEO 优化 里,不重复了。

天快亮的时候

那晚能修的几件都修完了,天已经蒙蒙亮。

回头看,它们有一个共同点:本地环境替你兜住了所有的脏东西。环境变量自动加载,文件写哪儿都在,端口随便连,浏览器和服务器在同一台机器上,所以没什么跨域和协议问题。

这些东西在你自己的机器上是「默认对」的,上了公网就全变成「默认错」的。你没做错什么,只是从来没人要求你想过它们。

所以我后来给自己写了一份清单,每次上线前照着过一遍。它现在就放在这个站上,叫上线自查清单,十分钟能过完,比通宵便宜多了。清单上的每一步背后都有一篇教程,从发布到公网开始。

下次部署完,别急着发链接。自己从头点一遍,把每个按钮都按一下。

相关文章