把首页那张图从 70 秒救回 1 秒
先说结论:问题不在 Cloudflare。
Worker 启动只花了 12 毫秒,HTML 也是边缘缓存命中的。真正把首屏拖死的东西,是我自己写的一段"优化"代码。
起因是一张破图
上个月换域名之后,陆续有人跟我说首页那张插画要刷两次才出来。我自己试了几次,大部分时候是好的,就当成网络抖动放过去了——人在给自己找理由的时候,效率总是特别高。
直到前两天朋友发消息问我:你首页的图是不是挂了。
截图里那块是空的,只有一个很细的破图边框。
我打开 Network 面板,看见那张 98KB 的 avif 请求状态是 502。刷新,好了。再刷新,又 502。三次里能挂两次。
单独测,问题很清楚
图片本身的地址长这样:https://api.xxx/file/1786609850258_1755572677646_1.avif,直连,0.9 秒。
但页面上渲染出来的地址不是它,是 /cdn?u=https%3A%2F%2F...——走我自己的 Worker 代理。
第一次请求:70 秒。
一张 98KB 的图,70 秒。我盯着这个数字看了很久,怀疑是不是 Chrome 的问题,换了个浏览器还是 70 秒。
再请求一次:1.5 秒。第二次就正常了。
这就是那种最讨厌的 bug:它不是一直坏,它是"每个边缘节点的第一个访客替所有人挨这一刀"。而我每次自己测试,点到的大概率是已经热过的节点。
为什么会 70 秒
图床跑在国内的一台机器上,Worker 的边缘节点在境外。代理这条路的成本是:浏览器 → 边缘 → 跨境回源图床 → 边缘 → 浏览器。图床本身没问题,0.9 秒就给数据,但中间那次跨境往返冷启动极慢,还要等 TLS 握手。
我当初加这个代理的逻辑是"第三方图床在国内慢,走 CF 边缘缓存会快"。这个判断对国外的图床成立,对一台就在国内的图床是反的——我给它多绕了一圈地球。
更让我难受的是另一半
翻代码的时候发现,真正蠢的不是代理慢,是同一张图被下了两遍。
layout() 里有个函数,会把页面里所有外链图片改写成 /cdn?u=。然后 <head> 里的 preload 用的是原始地址。
两个地址不一样,浏览器就当成两张图。
于是 preload 先下图床那张,img 又去下代理那张。98KB 的图,两份流量,第二份还得等 70 秒。
而 preload 是我加的,代理也是我加的。两个都写着"优化"。
改了三处
一,图床的图不再走代理。proxyExternalImages() 里加了一个白名单,命中自己的图床域名就原样返回。改完 hero 图的耗时从 70 秒变成 1.1 秒。
二,让 preload 和真实的 src 是同一个字符串。现在两者都对得上,浏览器只下一次。
三,顺手把字体从 CSS 里搬出来了。原来的做法是把四个字体文件 base64 塞进 /fonts.css,一坨 787KB 的 CSS。base64 会把二进制撑大三分之一,590KB 的 woff2 变成了 787KB,而且浏览器必须把这一大坨全下完,才知道字体在哪。现在 /fonts.css 只有 0.6KB,里面是四个链接,字体走独立路由返回原始二进制,缓存标 immutable。第一次访问就省了 197KB。
数字对比
| 改之前 | 改之后 | |
|---|---|---|
| 首屏那张图 | 70 秒(冷启) | 1.1 秒 |
/fonts.css |
787KB | 0.6KB |
| 字体本身 | 787KB(base64) | 590KB(原始 woff2) |
| 页面里的外部请求 | cdnjs + dicebear 头像 | 0 |
最后一行是我没想到的收获。图标是从 font-awesome 拉的一个 30KB CSS 加 77KB 字体,而全站只用了 44 个图标。我把字体裁到只剩这 44 个字形,4.6KB,内联进 HTML。导航头像原来调 api.dicebear.com 生成,一个 1.1KB 的 SVG 要跨境请求快两秒,现在直接在服务端拼一个首字母 SVG 塞进去,零请求。
一点教训
我原来以为性能优化是"加东西":加缓存、加代理、加 preload。这次三个问题反过来:都是加出来的。
代理是为了加速,结果绕远路。preload 是为了早点开始,结果下了两份。base64 内联是为了少两个请求,结果多了 33% 体积。
现在我的判断标准改了一句话:任何"优化"上线后,得量一遍再睡觉。 不然你不知道自己是在优化,还是在给一年后的自己埋一颗定时炸弹——它还会伪装成"偶发的网络问题"。

暂无评论,来抢沙发吧~