ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Vue3 项目压缩选型:gzip 与 Brotli 的底层原理、实测对比与 Nginx 落地

2026/9/15 0:24:15 拓冰建站 浏览量
Vue3 项目压缩选型:gzip 与 Brotli 的底层原理、实测对比与 Nginx 落地 第一次用 Vite 把一个 Vue3 后台管理模板打包上线时我盯着 dist 目录里那近 2MB 的 JS 文件犯了难Nginx 的 gzip 默认没开vite-plugin-compression 的 README 说能一把梭生成 gzip 和 brotli可到底用哪个再到网上搜一圈gzip 和 Brotli 哪个更好的答案各执一词有说 Brotli 压缩率吊打 gzip 的有说 CDN 不支持白搭的还有直接甩了个 solver 配置让人自己试。这篇文章我不打算复述文档而是从 DEFLATE、gzip、Brotli 三者的底层关系讲起用我实际测过的 Vue3 项目数据说明Vue3 项目到底适合哪种压缩以及在 Vite 构建和 Nginx 落地时最容易踩的坑。如果你是正在给后台管理系统做首屏优化的前端或者刚被 gzip: stdin: invalid compressed>npm i -D vite-plugin-compression然后在vite.config.ts里同时生成.gz和.brimport { defineConfig } from vite import vue from vitejs/plugin-vue import viteCompression from vite-plugin-compression export default defineConfig({ plugins: [ vue(), viteCompression({ algorithm: gzip, ext: .gz, threshold: 10240, deleteOriginFile: false, filter: /\.(js|mjs|css|html|json|svg|ttf|otf)$/i, }), viteCompression({ algorithm: brotliCompress, ext: .br, threshold: 10240, deleteOriginFile: false, compressionOptions: { params: { [require(zlib).constants.BROTLI_PARAM_QUALITY]: 6, }, }, filter: /\.(js|mjs|css|html|json|svg|ttf|otf)$/i, }), ], })这里有几个细节要解释。algorithm: brotliCompress就是 Node zlib 的brotliCompress它可以接收compressionOptions来设置等级如果你不传vite-plugin-compression 用的默认值是zlib.constants.BROTLI_PARAM_QUALITY: 11。也就是说很多人不设置这个参数构建时会在 brotli -11 上白白等十几二十秒这个坑我踩过。filter正则的作用非常重要插件生成.br后如果不加 filter第二个插件在遍历产物时可能把刚生成的.br再当原文件处理一遍形成.br.br。用白名单正则把源文件扩展名锁死就能避免这种恐怖的套娃。threshold: 10240表示只有大于 10KB 的文件才参与压缩小于这个值的文件压缩收益有限直接跳过能省下不少构建时间。构建完成后 dist 里应该每个 JS/CSS 文件都有两个兄弟.gz和.br。这样做的最大好处是压缩动作发生在构建期运行时服务器只负责读文件CPU 开销趋近于零。4.2 Nginx 配置brotli_static 配合 gzip_staticNginx 原生只带 gzip 模块brotli 需要额外编译ngx_brotli模块。如果你用的是 openresty 或者自己编译 Nginx在 configure 阶段加上./configure --with-compat --add-module../ngx_brotli编译安装后核心配置是这样的gzip on; gzip_static on; gzip_vary on; gzip_comp_level 6; gzip_min_length 1k; gzip_types text/plain text/css application/javascript application/json application/xml text/xml application/svgxml font/ttf font/otf; brotli on; brotli_static on; brotli_comp_level 5; brotli_min_length 1k; brotli_types text/plain text/css application/javascript application/json application/xml text/xml application/svgxml font/ttf font/otf;这里面的逻辑是当brotli_static on时Nginx 收到请求会先看磁盘上有没有对应的.br文件如果有且浏览器Accept-Encoding里包含 br就直接返回这个文件加Content-Encoding: br。如果浏览器不支持 br会继续走gzip_static找.gz。如果两个都没有才会落到gzip on实时压缩。所以同时开启三个开关并不会冲突它们是有优先级的降级链条。brotli on和brotli_static on是两个独立的开关。我见过只开了brotli on没开brotli_static on的配置结果 Nginx 每次请求都实时压缩一遍白白浪费 CPU。记住只要构建产物里已经有.br就一定要开brotli_static。4.3 方案二Nginx 实时压缩什么时候才值得用如果你不想在构建期生成预压缩文件也可以只用gzip on和brotli on让 Nginx 实时压缩。这种方案的优点是配置极简、文件目录干净缺点是每个请求都要现压CPU 压力大。实测下来低流量内部系统每分钟几百请求完全没问题但一旦上了生产且访问量较大CPU 使用率会明显爬升尤其是 brotli 等级超过 5 的时候。我的建议是实时压缩只作为一种兜底手段存在而不是主力。主力永远是构建期预压缩。如果实在不想预压缩那么 gzip 等级别超过 6brotli 等级别超过 5。4.4 CDN 和 HTTPS 下的注意事项如果你的 Vue3 项目套了 CDN事情会复杂一层。首先确认 CDN 是否支持 Brotli 透传源站在Accept-Encoding: br时返回Content-Encoding: brCDN 需要原样把响应头和内容回给浏览器而不能自作主张重新压缩。很多 CDN 默认是支持 br 的但如果你在源站同时开了 gzip 和 brotliCDN 边缘节点可能缓存了 gzip 版本然后发给请求 br 的浏览器——这时候浏览器解压就乱套了。正确的做法是确保响应头里有Vary: Accept-Encoding。Nginx 的gzip_vary on会自动加这个头CDN 才有依据区分缓存版本。还有一个隐蔽问题如果源站开了预压缩CDN 也开了压缩那么可能出现浏览器收到Content-Encoding: br但实际内容是被 CDN 二次压缩过的 gzip 数据这种连环错位。这种问题排查起来非常痛苦所以 CDN 场景下要么完全交给 CDN 压缩要么完全由源站预压缩不要两头都开。5. 踩坑实录从 invalid compressed>curl -I -H Accept-Encoding: gzip https://example.com/assets/index-abc123.js看返回的Content-Encoding和Content-Length。如果服务器返回了Content-Encoding: gzip再用curl -s -H Accept-Encoding: gzip https://example.com/assets/index-abc123.js | head -c 4 | xxd看前几个字节是不是1f 8b。如果不是说明这个gzip是假的。第三步检查服务器磁盘上的文件。进入 dist 目录file dist/assets/index-abc123.js file dist/assets/index-abc123.js.gz如果.gz文件显示 gzip compressed data而.br文件显示 Brotli compressed data文件本身没问题。如果发现.gz文件实际是 Brotli 压缩的那就是构建插件配置串了重点检查 vite-plugin-compression 两次调用时algorithm和ext是不是配对正确。第四步检查 Nginx 配置里静态文件匹配规则。我遇到那次问题的根源是Nginx 配置里写了location ~* \.(js|css)$的 alias 规则同时开了gzip_static on但 alias 指向的目录里放的是.br文件gzip module 找不到.gz于是直接返回了 404 或错误内容。这类问题最隐蔽因为它只在特定 location 和特定文件存在情况下触发。最后如果涉及 CDN还要查 CDN 边缘缓存节点上的Vary头是否正确。浏览器第一次请求没有Accept-Encoding: br时缓存了 gzip 版本CDN 没有 Vary 头后面带 br 的请求也会命中这个旧缓存然后一边解压一边报 format violated。5.3 缓存、Vary 与线上诡异白屏接上一个坑缓存错位是线上白屏的原罪之一。Vue3 构建产物默认带 hash 文件名正常情况下发布新版本会自动换 URL能规避大部分强缓存问题。但如果你手动部署时把 index.html 设为长缓存或者 CDN 上保留了旧版本资源那么浏览器拿着旧的 HTML 去请求旧的 JS服务器却因为路由配置返回了新版本的index-abc123.js内容和 hash 对不上就可能触发各种解压错误。一个实用的防御习惯是在 Nginx 里对 HTML 文件禁止缓存对带 hash 的静态资源开启长缓存同时保证Vary: Accept-Encoding头存在。Vite 的 hash 命名天然适合这套策略这也是我为什么一直建议不要关闭 Vite 的文件指纹功能。6. 结论Vue3 到底选哪种我的最终建议6.1 决策清单直接给结论按场景分场景首选方案备选方案理由自有服务器能编译 Nginx 模块构建期生成.br.gzbrotli_staticgzip_static只生成.gzBrotli 省 15% 到 20% 流量静态读取零 CPU 成本使用 CDNCDN 支持 Brotli构建期双算法CDN 透传让 CDN 自己压注意Vary头别搞二次压缩CDN 不支持 Brotli只用 gzip gzip_static-兼容性优先别为压缩率引入风险低流量内部系统Nginx 动态gzip on等级 6动态 brotli 等级 4 到 5配置简单CPU 压力可接受老项目还要兼容 IEgzip 为主brotli 按需IE 不支持 brVue3 没这个问题对绝大多数 Vue3 新项目我的默认方案是构建期同时生成 gzip 和 brotli 预压缩文件Nginx 里开brotli_static ongzip_static on让浏览器自动协商。这既能吃到 Brotli 的压缩红利又能在不支持 br 的极少数环境下自动降级到 gzip而且服务器 CPU 几乎零开销。6.2 在 CI 里固化一个产物检查脚本光配置到位还不够我强烈建议在 CI 构建脚本里加一个产物自检步骤防止某次构建因为插件版本升级、配置被误改而悄悄丢失压缩文件echo 检查 .br 文件数量 find dist/assets -name *.br | wc -l echo 检查 .gz 文件数量 find dist/assets -name *.gz | wc -l echo 检查是否存在误压缩的套娃文件 find dist/assets -name *.br.br -o -name *.gz.gz | wc -l如果.br数量明显少于 JS/CSS 文件数量或者出现了.br.br这种文件说明构建配置出了问题CI 直接 fail而不是等部署上线后从用户报错里才发现。6.3 最后一点个人体会我在这类优化上踩过不少坑最大的体会是大多数线上压缩问题不是选错了算法而是Content-Encoding和实际内容对不上。Brotli 给 Vue3 项目带来的 15% 到 20% 传输体积下降非常可观尤其对于后台管理系统这种堆了 Element Plus 和 ECharts 的项目值得上。但它不是救世主——路由懒加载、组件库按需引入、ECharts 拆包这些基本功做好了压缩算法才是锦上添花。反过来每次看到有人把 brotli 等级调到 11 来优化一个三五百 KB 的小项目时我都想提醒一句先去把那个躺着不动的 2MB 依赖拆了比什么都管用。