打开工作区以后,我先看的是一个月没有碰过的同步任务。页面还能打开,API 也能返回 200,这些结果让人很容易产生一种错觉:只要把上游版本拉下来,博客就算重新更新了。
但“更新”这个词在这里至少有三种意思。前端代码有没有同步到新的提交,线上页面有没有用这份代码重新构建,后端和数据库有没有经过兼容性验证。它们经常在同一天发生,却不是同一件事。
先看同步这一层
这次前端的 Sync Upstream 已经有一条完整通过的运行记录。它解决的是同步工作区里的几个具体问题:定时运行和手动重试不能同时改写同一条分支,安装依赖要跟着上游声明的 pnpm 版本走,生成提交时也不能让上游的提交钩子把镜像工作区重新 stash 一遍。
这条绿色记录很重要,但它只证明脚本完成了抓取、安装、测试、构建、提交和推送。它没有替 Vercel 重新跑一遍项目设置,也没有替我打开每一个页面。后来部署环境还暴露过设计系统子模块和冻结锁文件之间的不一致,说明“同步成功”与“部署成功”中间确实隔着一段路。
再看页面这一层
当前两个自定义域都能打开,后端的 ping、health 和 info 也能返回。首页没有浏览器挑战,公开 Feed 也能拿到 XML。读者看到的只是一张“现在可以访问”的照片,不是完整的变更证明。
完整的回归至少要再做几件小事:文章列表和详情要能互相对应,手记列表不能把私有对象拼进活动流,页面的标题不能渲染两次,RSS 的条目身份要和 API 返回的身份一致。每项检查都很琐碎,却各自挡住过不同的问题。
我以前会把这些检查合并成一句“线上正常”。现在不太敢这么写。一个页面返回 200,只能说明这一条请求走通了;它不代表缓存已经失效,也不代表下一次构建会继续使用正确的安装根目录。
后端的“最新”更慢一点
公开 API 读回的后端版本仍然是 v13.25.3。上游已经有更新的版本,但版本号更大并不意味着可以直接替换生产。后端更新会牵动数据库迁移、后台接口、评论和草稿链路,最先要验证的,是数据库能不能从备份恢复、迁移失败能不能停在原处、旧的文章和手记接口还能不能按原来的契约返回;“能不能启动”反而要排在后面。
这也是为什么我没有把“一个月没有更新”简单地写成“赶紧升级”。如果生产现在还能稳定提供页面和 API,先把预演环境搭起来、把回滚包做出来,再决定什么时候切换,反而比在晚上直接追最新版本更接近维护本身。
文章也有自己的部署门禁
内容写回和代码发布看起来是两条线,其实共享同一个习惯:不要只看写入时的成功响应。标题、slug、正文、日期和公开状态需要在同一个对象上读回,列表、详情、搜索和 RSS 还要再看一次。否则很容易出现后台已经改了,页面仍在读旧缓存;或者正文没有公开,活动列表却留下了一行看不懂的占位。
我现在更愿意把“没有更新”拆成一张小清单:哪些提交没有同步,哪些部署没有发生,哪些 API 还没读回,哪些数据库迁移没有预演,哪些页面只是暂时返回 200。清单不会让升级变快,但它能把焦虑从“是不是全都坏了”变成几个能够分别回答的问题。
一个月没有更新以后,最先需要做的并不是把版本号往前拨,而是重新确认每一层的当前状态。等这些状态都留下证据,再决定是继续等待、做一次小修复,还是安排完整发布。更新因此不再只是追赶时间,也是一种对边界负责的速度。