上个月有个客户找我,说站点访问慢,服务器带宽才 5M,是不是该升到 10M。我看了一眼他的 Nginx 配置,没说话问题根本不在带宽上。一个不大的前端站点,主 JS 包 2.4MB,服务器每次都在原样传输,没有开 gzip,也没有 Brotli。用户每打开一次页面,就要从服务器下载 2.4MB 明文,再由浏览器自己解压。这就好比你把压缩包原封不动地寄给客户,收件人自己负责拆包,寄费当然高。
如果你的站点也在做静态资源分发,或者正在为一台机器的带宽和流量发愁,这两家可以先看看配置和报价:lightlayer(独立物理服务器,大带宽与高流量场景)、vollcloud(香港 CMIN2 大带宽,吞吐量最高 5G/s)。静态资源传输最吃的就是出口带宽和月流量配额,选机器时这两项比 CPU 重要得多。下面把整条链路捋清楚。

一、先搞清楚你到底浪费了多少流量
动手改配置之前,先测一下。在服务器上执行:
curl -s -H "Accept-Encoding: gzip" -o /dev/null -w "%{size_download}\n" https://your-site.com/app.js
这个命令会告诉你在请求声明支持 gzip 的情况下,服务器实际发给你多少字节。再执行一次不带 Accept-Encoding 的:
curl -s -o /dev/null -w "%{size_download}\n" https://your-site.com/app.js
两个数字一比,差距就是你白烧的带宽。我那个客户的前端包裸传 2.4MB,开 gzip 后 612KB,压缩比 3.9:1。按他每天 3 万次访问算,一个月多传出去大概 48GB 流量在按流量计费的机器上,这笔钱够再买一台机器了。

二、gzip 到底做了什么
gzip 是最经典的通用文本压缩算法。它的原理不复杂:把文件里重复出现的字符串用「回指」代替比如一段代码里出现了 20 次 function,压缩后只存一次,后面用一个小标记指回去。JS、CSS、HTML、JSON、SVG 这些文本类资源,压缩比通常在 3 到 6 倍。
Nginx 里默认没有开启 gzip,需要自己写进 /etc/nginx/nginx.conf 的 http 段:
gzip on;
gzip_vary on;
gzip_comp_level 5;
gzip_min_length 1024;
gzip_proxied any;
gzip_types text/plain text/css text/xml application/json application/javascript application/xml+rss image/svg+xml;
逐条说下为什么要这么写:
gzip on 是开关,没它后面都白搭。
gzip_vary on 会给响应加上 Vary: Accept-Encoding,告诉下游缓存「这个资源跟请求方的压缩支持有关」。这一条很多人漏掉,后果是 CDN 或中间代理把压缩版和未压缩版混着缓存,偶尔就出现乱码或者解压失败。
gzip_comp_level 5 是压缩级别,1 到 9。别无脑调到 9:级别 9 相比 5 通常只多压缩 3% 到 5%,但 CPU 耗时能翻几倍。用户端解压也是成本,压缩比和 CPU 得有个平衡。5 是绝大多数场景的甜点,静态大文件可以试 6,动态页面保持 1。
gzip_min_length 1024 表示小于 1KB 的不压缩。小文件压缩后可能反而变大(压缩头本身有开销),还会白白消耗 CPU。
gzip_types 是重点。Nginx 默认只压缩 text/html,JS 和 CSS 都不在内。很多人配了半天 gzip 没效果,就是因为漏了这一行尤其要注意 application/javascript,写成了 application/x-javascript 或干脆漏掉,都是白配。SVG 也别忘,图标库用 SVG 的话压缩收益很大。
改完执行 nginx -t 验证语法,再 nginx -s reload 热加载,不要重启。

三、Brotli 比 gzip 更狠,但它有坑
Brotli 是 Google 设计的下一代压缩算法,同等质量下压缩率比 gzip 高 15% 到 30%,而且对 JS 的压缩尤其友好它内置了字典,能预判常见的前端语法结构。在启用 Brotli 的站点上,1.8MB 的 JS 常常能压到 420KB 左右。
但 Brotli 有个前提条件:必须同时支持 gzip 作为回退。原因是 Brotli 支持度虽然已经很广(Chrome、Firefox、Safari、Edge 近几年版本都支持),但老浏览器、老安卓 WebView、部分企业代理仍然只认 gzip。正确的做法是:客户端支持就用 Brotli,不支持就退回 gzip,两套都留着。
Nginx 的官方 Brotli 模块需要编译第三方库(ngx_brotli)才能用,配置方式是:
brotli on;
brotli_comp_level 5;
brotli_min_length 512;
brotli_types text/plain text/css application/json application/javascript text/xml application/xml+rss image/svg+xml;
brotli_static on; 那行很关键:它让 Nginx 在磁盘上预先生成 .br 文件,每次请求直接发文件,连实时压缩的 CPU 都不用花。适合文件基本不变的前端资源,构建产物每次发版只动一次,压缩一次能用很久。配套的做法是在打包时就把 .br 文件一起产出,Nginx 直接用静态版本,这是最省资源的方式。
如果你的服务器环境不好编译,或者嫌维护麻烦,用 brotli_static + 打包时预压缩是最省事的路径,效果和动态压缩几乎一样。
四、压缩只解决一半,另一半在缓存头
很多站点 gzip 开了、也见效了,但用户抱怨「每次刷新都像重新加载一遍」。这不是错觉,而是浏览器缓存策略没配对。
核心是 Cache-Control 这个响应头。它告诉浏览器这个资源多久内可以直接用本地副本、根本不用问服务器。
location /static/ {
expires 30d;
add_header Cache-Control "public, max-age=2592000, immutable";
}
几个要点:max-age=2592000 是 30 天。immutable 告诉浏览器「这段时间内这个 URL 的内容不会变,你不用来问」。这个关键词特别适合带哈希文件名的构建产物比如 app.a3f9c2.js,内容变了文件名就变,URL 没变但浏览器根本不会来请求,所以 immutable 是安全的。
反过来,如果是 app.js 这种固定名字的不带哈希的文件,缓存 30 天就会导致发版后用户拿到旧版本。这就是很多老项目「缓存了之后更新不生效」的根源。对策要么给文件加哈希(webpack/vite 默认就这么干),要么用 must-revalidate 缩短缓存期。
另一个常被忽略的是 ETag 与 Last-Modified。它们的用途是协商缓存:浏览器带上 If-None-Match 来问「我本地这份还是最新的吗」,服务器回一个 304,不传 body。这个 304 响应体只有几十字节,相比重新传 600KB 的文件,省下 99% 的流量。Nginx 默认对静态文件就开了 etag on 和 if_modified_since,一般不用管。

五、顺手把 HTML 也管起来
除了静态资源,HTML 本身也别忘了压缩和缓存策略:
location / {
gzip on;
add_header Cache-Control "no-cache";
}
no-cache 的意思是「可以缓存,但每次用之前必须问服务器验证一下」,不是「不要缓存」。这个区别经常被搞混。HTML 用 no-cache 配合 ETag,每次访问只花一个 304 的代价;而带哈希的静态资源用 immutable 长缓存,压根不问服务器。两者配合起来才是完整的缓存策略。
还有个细节:no-cache 是响应头发给浏览器的,如果前面挂了 CDN,得在 CDN 侧也把缓存规则配对,否则 CDN 可能把旧 HTML 缓住,你的发版对所有用户都延迟生效。
六、验证:别凭感觉,用数据说话
改完之后按这几步验:
一、看响应头。curl -I https://your-site.com/app.js,确认有 Content-Encoding: gzip、Vary: Accept-Encoding、Cache-Control、ETag 这几项。
二、量传输量。用前面那个 curl -w "%{size_download}" 对比改前改后。
三、验 304。curl -I -H "If-None-Match: <上一步拿到的ETag>" https://your-site.com/app.js,应该看到 304 Not Modified。
四、看真实用户体验。拿 curl -w "TTFB:%{time_starttransfer} Total:%{time_total} Size:%{size_download}\n" 分别测一下首字节时间和总耗时,跟上线前对比。
五、别只看本地。用外部的拨测节点测一次,国内网络和海外访问的路径完全不同,本地跑得顺不代表用户那边也顺。

七、写在最后
回过头看那个 5M 带宽的站点,配置改完之后首屏从 4.2 秒降到 1.6 秒,带宽一点没加,月流量账单掉了三分之二。服务器性能问题里,有相当大一部分其实出在传输这一层不是机器不够快,而是同样的数据在路上跑了两遍、第三遍。
优化的顺序我建议是这样:先开 gzip(十分钟见效),再配 Cache-Control 和哈希文件名(解决重复下载),然后考虑 Brotli(有编译环境的话),最后才去动带宽和机器配置。顺序反了,钱花在最贵的地方,效果却最小。
另外提醒一句:静态资源做了长缓存之后,发版流程要跟着改必须用带内容哈希的文件名,否则用户会在缓存里待到过期才拿到新版本。这是开启强缓存后最常见的事故,比缓存没配更让人头疼。


喜欢
顶
难过
囧
围观
无聊



