此次优化的排查、代码修改、测试和部署,100% 由 Codex 完成。本文也由 Codex 根据工作记录总结,人工审核。
GapHub 上线有一段时间了,功能陆陆续续加了不少,但我自己用着总觉得卡。
有时第一次打开首页,会白好几秒。点一条帖子,页面停在原地,过一会儿才跳过去。我在对话里给 Codex 的原话是:
点完之后没有立刻跳转,我都怀疑我没点上。
本来就没几个人用,站长自己点个帖子还要猜,多少有点说不过去。
于是我把项目交给 Codex,让它从代码到 Supabase、Vercel 都过一遍。我不指定具体改哪里,但有一个要求:原来的功能要能用,布局、样式和交互别给我改坏了。
我负责提要求、看效果,有问题就继续反馈。工程上的活都由它来做,这一轮从昨晚跑到今天。
结果确实比我想的复杂。开始只是嫌页面卡,后来连数据库事务都修上了。
先把「没点上」解决掉
我最初还怀疑过 Next.js、Supabase、Vercel 这套组合是不是选错了。尤其是点链接之后原地等着的感觉,让我很想把它改成前后端分离:界面先动,接口慢慢请求,不就好了?
Codex 查下来,先改的是页面的等待方式。
原来有些页面要把几项数据等齐,才开始显示内容。用户信息、公告、正文、回复,只要前面有一项慢,后面就跟着等。接口虽然是异步的,但对坐在浏览器前的人来说,还是点了没反应。
这次把点击反馈和内容加载分开了。点链接,点击位置先有提示;切到目标页,先出现对应的骨架屏;真实内容回来以后,再把占位换掉。发帖、回复这些按钮也一样,点下去就显示提交中,不能等请求结束了才想起来告诉我。

这里不需要换框架。Next.js 本来就有 loading.tsx 和 Suspense 这些机制,可以在数据准备好之前显示占位。只是之前项目里没有把这些地方处理好。相关文档
不过,加了骨架屏以后,我又碰到了两个问题。
第一个是点 Logo 或面包屑回首页,导航出来了,下面还是空的。查了以后发现,有一层在等公告,那里只放了一小段占位;首页内部虽然有完整骨架,但还没轮到它显示。Codex 把外面这一层也补齐了。
第二个更容易漏掉。连续点进点出很快,但停下来读一会儿帖子,再返回首页,就又慢了。
这次查到了路由预取:原来的首页预取响应里,没有带上需要的骨架。最后给首页单独整理了加载边界,网址和正常布局都没动。修完后,再在帖子里停留一分多钟返回,骨架能及时出现了。
还有那根黑线。
最初为了提示正在跳转,页面顶部加了等待条。结果快进快出时,它就一闪而过。我又发了张截图问 Codex,这是什么东西?
后来改成点击处照常反馈,顶部提示超过 200 ms 才淡入,颜色也淡了一些。页面本来就切得快的时候,它不用出来刷存在感。
页面有反应了,内容也得早点回来
骨架屏看久了也烦,真实请求还是得查。
Codex 给服务端加了分段计时,看看一次请求到底在等身份、板块,还是帖子数据。之前我很容易把“接口花了一秒”直接理解成“数据库查了一秒”,实际中间还有好几层等待,得拆开看。
有些请求原来是在排队做,但其实可以同时开始。
比如普通公开板块,确认板块启用后,帖子列表的读取就可以和身份查询重叠。最后返回页面前,仍然要完成身份和权限检查。树洞等需要先判断访问权限的地方,继续按原来的权限顺序处理。
帖子详情也拆了一下。正文不用再等整批回复和回复者的信息都准备完,回复区有自己的骨架,慢一点就晚一点出现。如果回复读取失败,正文还能继续看,回复区单独重试。
还有一些重复查询。同一次请求里,标题、页面元信息和正文都要用到主题基础资料,就尽量复用这一份,而不是各查各的。
缓存也加在了比较合适的地方:变化不频繁的公共板块配置短时间缓存,改配置后让缓存失效;当前标签页里的用户展示信息复用一下,避免每换个页面都从头加载。真正发帖、扣费时,还是要让服务端重新核对身份和权限。
后台则改成用哪个页签读哪个页签的数据。打开板块管理,就没必要把其他几个管理页签的内容也全查一遍。
做完这些,技术栈还是那套技术栈,只是没必要串着等的地方少了一些。
比转圈更烦的,是转完以后不知道发生了什么
这轮还修了不少失败时的表现。
发帖失败,字不能没了。回复提交出错,按钮得恢复,不能一直卡在提交中。字数超限可以提示,但别直接把草稿截掉。资料保存出了问题,也要能接着改、接着试。
有一次线上验收,通知实际上已经删了,页面却长时间停在“删除中”。这种时候直接重发请求不太合适,因为服务端可能早就处理完了,只是浏览器没收到结果。
最后给长等待加了一个重新读取列表的入口,让用户先核对结果。读一次当前状态,比猜刚才那个操作到底有没有成功要靠谱。
用户摘要也加了 15 秒的总等待上限。超过时间后保留已有信息,给出重试入口。查不到余额的时候,不能随便显示成零;暂时确认不了身份,也不能直接当成退出登录。
另一个不太看得见的问题是请求回来得晚。旧请求可能在新的操作之后才结束,如果照单全收,就可能把刚更新的余额或身份信息又覆盖回去。这些地方也加了过时结果的处理。
图片重试按钮、编辑器行中插入格式等小地方,也顺手修了。都是平时单看不算大问题,连续用起来却挺影响心情的东西。
怎么还修到数据库里去了
我让它别破坏已有功能,Codex 就继续检查权限和管理操作。然后找到了两个能复现的问题:同一条到期处罚被并发恢复时,可能写出重复日志;人工解除处罚时,某一步数据库更新失败,前端却仍然可能收到成功提示。
原来的解除流程大致是分开改处罚记录、改账号状态、写日志。几个请求之间,只要有一步失败,就可能留下前面已经改过、后面没改完的状态。
这事就不能先放着了。否则用户明明看到“解除成功”,实际还是发不了帖,或者日志和账号状态对不上。
Codex 单独出了方案,确认后把这些修改放进数据库事务里。事务的作用可以简单理解为:这一组修改一起完成,中间有一步失败,就撤回这一组已经做的修改。PostgreSQL 事务说明

同时还要考虑两个请求撞在一起的情况。
相关操作会先锁住同一个账号的记录,拿到锁以后再检查最新状态。不能拿着几秒前读到的旧处罚,去把后来新加的处罚解掉。
治理操作还加了一个固定编号。如果操作已经成功,只是响应在路上丢了,再用同一个编号、同样的参数重试,可以找到之前的结果,不会重复处罚或重复记日志。这个机制是针对治理操作做的,并不是所有业务接口都自动有了它。
处罚什么时候到期,统一按数据库时间判断。发帖、回复、签到和资料修改等入口,也在事务里做最终的参与资格检查。费用和原有匿名规则没变。
这部分耗时不少,但既然问题已经找到了,还是修完再上线比较踏实。
结果怎么样
下面是 Codex 在这轮不同批次里留下的实测记录。

| 检查内容 | 结果 |
|---|---|
| 桌面连续首页、详情往返 | 20 次点击到下一次绘制约 18~38 ms |
| 同一组导航的路由首帧 | 约 29~59 ms |
| 停留后通过面包屑、Logo 返回首页 | 修复后两个样本约 51 ms、45 ms |
| 关闭浏览器缓存打开首页 | 三次 FCP 约 715~1130 ms |
| 两条多图长帖的布局偏移 | CLS 约 0.005 |
返回首页这项,修复前记录过一次约 488 ms 才出现路由首帧的情况。修复后这两个样本改善很明显,不过样本少、访问条件也不完全一样,不能拿来算一个全站提速百分比。
这些数字里,几十毫秒主要说的是反馈或首帧,首帧可能还是骨架。FCP 是第一次画出内容,也不等于帖子、回复、图片都加载完了。导航测试用的是桌面 Chrome,没有主动限制 CPU 和网速,缓存开启,也保留着原来的扩展。关缓存的三次首页访问仍然包含网络耗时,不能当成函数冷启动测试。
所以能说的是,已测的这些交互更及时了。真实内容仍然可能要等,后面也录到过一个接近 800 ms 的服务端首页慢样本。真实用户的整体表现和冷启动,还没有足够数据下结论。
除了看页面,Codex 跑了 lint、类型检查、生产构建和 456 项应用测试。数据库另用隔离的 PostgreSQL 17,测试并发恢复、失败回滚、重复请求,以及权限和收费规则。线上则验证正常发帖、回复、补充、签到、资料保存等操作,临时测试内容用完清理,没有拿真实用户做处罚实验。
数据库修改也是分步上的:先加兼容接口,再部署应用,确认主域不再把治理请求送到旧版本,最后开启新的事务写保护。避免代码已经换了,数据库还没跟上。
改完之后
这一轮下来,GapHub 还是原来的样子,底下也还是 Next.js、Supabase 和 Vercel。改动主要发生在平时用起来觉得别扭的地方:点链接有反馈,返回首页有骨架,正文不用等回复一起加载,提交失败还能接着改,管理操作的成功提示也有了事务保证。
实测里,桌面连续导航的点击反馈到了几十毫秒,停留后返回首页的两个样本也能在约 50 ms 出现首帧。内容加载仍然需要时间,但至少不用盯着一个没动静的页面,猜自己刚才有没有点中。
原本只是想修一下卡顿,最后把加载、出错和数据写入都梳理了一遍。这些地方补齐以后,之前已经做出来的功能,才算用得更顺手了。