Nginx 开启 gzip 之后,传输量到底降了多少
给一个静态站点做首屏优化时,先量了一遍传输量:单个 JS 产物 1,165,571 字节, 单个 CSS 373,953 字节。当时第一反应是去做代码分割,后来发现更简单的原因—— 这两个文件根本没有被压缩过。
怎么发现的
用 curl 带上 Accept-Encoding 请求头,看响应里有没有 Content-Encoding:
curl -sI -H 'Accept-Encoding: gzip, br' \
https://example.com/assets/app.js | grep -iE 'http/|content-length|content-encoding'
返回的头里只有 Content-Length: 1165571,没有 Content-Encoding。
而同一站点的 HTML 首页却带着 Content-Encoding: gzip。
这个差异直接指出了问题所在。
为什么 gzip on 单独写没有用
打开 /etc/nginx/nginx.conf,能看到这样的片段:
gzip on;
# gzip_vary on;
# gzip_proxied any;
# gzip_comp_level 6;
# gzip_types text/plain text/css application/json
# application/javascript text/xml application/xml application/xml+rss
# text/javascript;
关键在最后一行:nginx 的 gzip_types 默认值只有 text/html。
所以 gzip on 生效的范围内,只有 HTML 会被压缩,
JS、CSS、JSON 全部按原样传输。这正好解释了上面观察到的现象。
改法
既可以在 http 块里统一配置,也可以在单个 server 里配置。这次只对一个站点生效, 所以写在了 server 块中:
server {
listen 80;
server_name example.com;
gzip on;
gzip_vary on;
gzip_comp_level 6;
gzip_min_length 512;
gzip_types
text/plain
text/css
text/xml
application/javascript
application/json
application/xml
image/svg+xml;
# ...
}
改完先校验再热加载,避免把整个站搞挂:
nginx -t && systemctl reload nginx
实测结果
| 资源 | 压缩前 | gzip 后 | 节省 |
|---|---|---|---|
| app.js | 1,165,571 B | 385,784 B | 67% |
| app.css | 373,953 B | 约 60 KB | 84% |
文本类资源的压缩比通常都在 60%–80% 之间,只改几行配置就能拿到, 性价比远高于为同样体积去做代码拆分。
几个容易漏掉的点
gzip_vary
加上 gzip_vary on 会输出 Vary: Accept-Encoding。
如果前面有 CDN 或缓存层,不加这个头可能出现「压缩版和未压缩版发错对象」的问题——
支持压缩的客户端收到了未压缩内容,或者反过来。
gzip_proxied
通过 proxy_pass 反代到后端的接口,默认不会被压缩。
要让 nginx 对上游响应也做 gzip,需要显式打开:
gzip_proxied any;
但要注意,如果后端已经把响应压缩过一次(比如 Express 的 compression 中间件), 这里再开会导致双重压缩,需要二选一。
gzip_static
如果构建阶段能产出 .gz 文件,可以用 gzip_static always
直接发预压缩版本,省掉运行时的压缩开销。适合访问量大、内容不常变的静态资源。
验证是否真的生效
# 有 gzip 时
curl -sI -H 'Accept-Encoding: gzip' https://example.com/assets/app.js \
| grep -i content-encoding
# 期望输出: Content-Encoding: gzip
# 不带 Accept-Encoding 时应无压缩
curl -sI https://example.com/assets/app.js | grep -i content-encoding
# 期望无输出
两条都符合预期,才算配置真正到位。只看配置文件容易被 include 顺序、 默认值覆盖这类问题坑住。