上周三晚上十一点,我把 nginx.conf 改了第七遍。
站点跑在一台 2016 年的笔记本上,塞在书架下层,风扇声音像要起飞。这台机器同时在干别的事,所以我对它的性能一直不抱期待——打开慢一点,我认。
但那天的问题不太一样。首屏会闪一下:文字先用一种字形出现,半秒后跳成另一种。不是卡顿,是「跳动」。我盯着看了十几遍,确定不是错觉。
我最初判断这是带宽问题。错得有点典型。
一开始我以为是带宽
打开 DevTools 的 Network 面板,按体积排序。首页 HTML 4.2 KB,样式表 8.6 KB,favicon 一 KB 出头。加起来不到 15 KB。
这个数字让我愣了几秒。15 KB 的东西不存在「加载慢」的问题,哪怕走的是我家那条不算快的宽带。带宽这个方向从第一步就错了。
gzip 开了,但没解决那个闪
既然在看配置,顺手还是把 gzip 开了。
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_comp_level 5;
gzip_types text/plain text/css application/javascript application/xml image/svg+xml;
comp_level 我停在 5,没有往上调。6 到 9 那几档在这点体积上省下的字节数是个位数百分比,CPU 却要多花不少。一个 15 KB 的页面,为几十字节去榨 CPU 不划算。
重新加载,样式表从 8.6 KB 掉到 3.1 KB。数字上有效果。
但那个闪还在。
真正的原因是字体
打开 Rendering 面板,勾上「Paint flashing」,我才看清楚:闪的那一下,是文字从系统默认字体切换过来。
我的样式表里写了这样一串字体栈:
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC",
"Microsoft YaHei", "Noto Sans CJK SC", sans-serif;
问题不在这行本身。问题是我同时给正文另指定了一个衬线字体,而那台机器上没有这个字体——浏览器先拿默认字体顶上,等它逐个找完候选、确定最终用哪个,中间就露出了那半秒。
修法很简单:把字体栈收短,只留系统里确定存在的几项,正文不再另指定衬线体。改完,「闪」消失了,同时页面少了一轮字体匹配。
代价是版式没那么「文气」了。我犹豫了一阵,最后觉得不闪更重要。
顺手把缓存头也理了一遍
字体的事弄清之后,我回头检查缓存策略,发现两个问题。
一是 HTML 被我设了长缓存。这很糟:免责声明、隐私政策这类内容改完必须立刻生效,读者拿到七天的旧页面是说不清的。
二是静态资源根本没有缓存头,每次访问都重新请求。
改成分开处理:
location ~* \.(css|js|svg|png|ico|webp)$ {
expires 30d;
add_header Cache-Control "public, max-age=2592000, immutable";
}
location ~* \.html$ {
expires -1;
add_header Cache-Control "no-cache";
}
immutable 是后来加的。资源文件名里不含哈希,所以严格说这不算最佳实践——但个人站点的资源很少改,真改了我就手动换个文件名,比给每个文件算一遍哈希省事。
还没弄明白的地方
gzip_vary on 这行加上去之后,我一直没验证过它到底有没有生效。理论上它会给响应补一个 Vary: Accept-Encoding,让中间缓存把压缩版和未压缩版分开存。我这个站前面既没有 CDN 也没有反向代理,那这行是纯冗余、还是有我没想到的用处,我没查清楚。
先留着。留着的成本接近零,去掉的风险我没评估过。
现在的样子
首页在冷缓存下大约 200 毫秒出内容,首屏不再闪。总传输量从 14.8 KB 降到 6.3 KB。
老实说,这次真正解决问题的是那行字体栈,gzip 和缓存头都属于「既然在看就一起弄了」。性能这件事,先量再改比先改再量省事得多——我这次是反过来的,白折腾了六遍配置。