一点四十几分,我们还在群里互相发「你再刷一下」。

事情不复杂。客户后台一个表单要加条手机号格式校验,前端是外包的兄弟写的,我负责测。他改完发我,我点提交,没反应。

「没生效。」

「你刷新了吗?」

「刷了。」

「强刷,Ctrl+F5。」

按了。F12 开着,Network 里那个 app.js 是 200,Size 那栏写着 (disk cache)。我看了一眼没多想——缓存嘛,强刷一下应该就没了。

再点。还是没反应。

于是就这么来来回回。他改一版,我测一版。中间他把判断条件都换了,我还是在控制台里手动敲那个函数看返回值,返回的是老逻辑。两个人隔着屏幕,对着同一段「明明已经改了」的代码看半天,谁也没往别处想。

前端那边键盘敲得噼啪响,我这边一遍遍点提交。快两点的时候他发消息说这次真改了,你再看。我看了,没变。他说不可能。我说你自己看。他说你缓存没清吧。我说我清了八百遍了。

这种时候最容易抬杠。

到两点半,第四版了。这回我没急着回他,先去看那个 js 到底是从哪加载的。地址不是我们自己的域名,是一个静态资源域名。然后脑子里嗡一下——这站静态资源走 CDN。他改的是源站的文件,浏览器拿的一直是 CDN 边缘节点上那份。源站上他改十遍,只要 CDN 上那份没过期,我这边看到的就永远是旧的。

我在群里打了一行:会不会是 CDN 缓存。

他那边停了一小会儿,回了两个字:我靠。

我把那个 app.js 的响应头翻出来,两行:

Age: 1806
cache-control: public, max-age=86400

Age 一千八百多秒,也就是半小时前。max-age 86400,一天。我俩对着一个一天前的老缓存,认认真真测了两个多小时。

后来复盘,发现最坑的不是 CDN 缓存本身,是它太安静了。资源照常返回 200,浏览器照常渲染,控制台不报错,功能也跑着——只不过跑的是旧代码。所有信号都告诉你没问题,唯独结果不对。这跟一上来就报 500 的故障完全两码事,报错的反而好查,这种「一切正常但就是不对」的,最耗人。

也有我自己的坑。Ctrl+F5 清的是浏览器本地缓存,清不掉 CDN 边缘节点上的缓存——这个我本来知道,但那天到那个点,脑子里就是没这根弦。人熬到后半夜是真的会变笨。我那天看自己写的测试步骤都觉得陌生,一个摆在眼前的东西愣是视而不见。前端那兄弟也一样,他改到后面开始怀疑是不是构建没跑,其实构建早跑过了。谁也别笑话谁。

真想解决其实不难,都是发布流程的事:

静态资源加版本号,app.js?v=xxxx 或者构建出来的文件名直接带 hash。名字一变,CDN 就当成新文件重新回源,根本不用等缓存过期。

或者发布完顺手刷一次 CDN 缓存,很多平台都有 API,能塞进脚本里自动做。

缓存策略也该分开:html 这种入口文件别缓存、或者短缓存;带 hash 的 js/css 给长缓存,反正内容不变名字就不变,过期早晚无所谓。

我们现在是前两条都加上了,版本号加发布脚本里带一步刷缓存。那次之后,类似的「改了没生效」再没出现过。

说到底是代码一行没错。错的是我们俩对了一宿的时间差——他改的是源站,我看的是缓存。凌晨收工的时候窗外都发白了,我关电脑还在想,明明半小时能搞定的事,怎么拖成这样。

可能这就是熬夜的代价,脑子跟不上手。第二天我俩谁也没先提测试报告,先睡了一觉。