静态资源预压缩:零运行时开销,极致节省带宽

前言

你也许在 nginx 上配置过以下内容:

http {
    gzip on; 
    gzip_types application/atom+xml application/javascript application/json application/vnd.api+json application/rss+xml application/vnd.ms-fontobject application/x-font-opentype application/x-font-truetype application/x-font-ttf application/x-javascript application/xhtml+xml application/xml font/eot font/opentype font/otf font/truetype image/svg+xml image/vnd.microsoft.icon image/x-icon image/x-win-bitmap text/css text/javascript text/plain text/xml;

    server {
        // ...
    }
}

上面配置文件中的 gzip on; 即为启动动态压缩:占用服务器的 CPU 和内存资源实时进行压缩。
容易想到,我们可以将文件进行预压缩,在运行时直接提供压缩好的文件。

原理

构建时预压缩,以高压缩等级生成对应文件,服务器在运行时直接提供压缩后的文件。

一般约定:

算法 文件后缀 普及情况
gzip .gz gzip
brotli .br brotli
zstandard .zst zstd

例:如果你看到 1.js.br,说明是由 1.js 使用 brotli 算法预压缩生成的文件。

注:截止于 2026 年 2 月 12 日,zstd 未进入 baseline。建议当前仅部署 gzip 和 brotli 预压缩版本。

优势和缺点

预压缩有以下好处:

  • 节约服务器内存资源。
  • 节约服务器 CPU 资源。
  • 节约服务器带宽。

缺点:

  • 需占用更多的存储空间。
  • 编译时需消耗更多的 CPU 和内存资源,以及更长的耗时。

如何配置

通用配置分享

  • 适合预压缩的文件后缀:

    • 正则表达式形式:/\.(atom|rss|xml|xhtml|js|mjs|ts|html|json|css|eot|otf|ttf|svg|ico|bmp|dib|txt|text|log|md)$/
    • 可根据实际情况补充其他文件,例如:conf, ini, cfg。
  • 对应 MIME:application/atom+xml application/javascript application/json application/vnd.api+json application/rss+xml application/vnd.ms-fontobject application/x-font-opentype application/x-font-truetype application/x-font-ttf application/x-javascript application/xhtml+xml application/xml font/eot font/opentype font/otf font/truetype image/svg+xml image/vnd.microsoft.icon image/x-icon image/x-win-bitmap text/css text/javascript text/plain text/xml text/html

    • 根据文档,nginx 配置 gzip_types, brotli_types, zstd_types 时,均无需填写 text/html。不论 *_types是否包括text/htmltext/html 始终会被动态压缩。相关文档:gzip_typesbrotli_typeszstd_types

构建配置分享

适用于 Vite 的配置

安装 vite-plugin-compression2 插件进行预压缩:

选择合适的安装方式:

npm install vite-plugin-compression2 -D
pnpm add vite-plugin-compression2 -D
yarn add vite-plugin-compression2 -D

安装完成后配置 vite.config.ts

import { constants } from "node:zlib";

import { compression, defineAlgorithm } from "vite-plugin-compression2";

export default defineConfig({
  // 其他配置
  plugins: [
    // 其他插件
    compression({
      algorithms: [
        // 设置为最大压缩等级 9
        defineAlgorithm("gzip", { level: 9 }),
        // 设置为最大压缩等级 11
        defineAlgorithm("brotliCompress", {
          params: {
            [constants.BROTLI_PARAM_QUALITY]: 11,
          },
        }),
        // 最大压缩等级是 22,内存消耗量较大。如构建失败,可设置为 21,20,或去除这段。
        defineAlgorithm("zstandard", {
          params: {
            [constants.ZSTD_c_compressionLevel]: 22,
          },
        }),
      ],
      include: [
        /\.(atom|rss|xml|xhtml|js|mjs|ts|html|json|css|eot|otf|ttf|svg|ico|bmp|dib|txt|text|log|md|conf|ini|cfg)$/,
      ],
    }),
  ],
  // 其他配置
});

注意:此插件与 VitePress 兼容性不佳,会导致生成的 .js 预压缩文件中出现多余的 __VP_STATIC_START__ and __VP_STATIC_END__ 标记。

部署配置分享

在 nginx 上使用

http {
    # nginx 会根据 Accept-Encoding 决定提供哪种格式的文件,因此不同算法配置顺序不影响结果。

    # 启用 gzip_static 模块以提供预压缩的 .gz 文件
    gzip_static on;

    # 如果找不到静态文件则回退到动态压缩
    gzip on;
    gzip_types application/atom+xml application/javascript application/json application/vnd.api+json application/rss+xml application/vnd.ms-fontobject application/x-font-opentype application/x-font-truetype application/x-font-ttf application/x-javascript application/xhtml+xml application/xml font/eot font/opentype font/otf font/truetype image/svg+xml image/vnd.microsoft.icon image/x-icon image/x-win-bitmap text/css text/javascript text/plain text/xml;
    # 用于在 HTTP 响应头中添加 Vary: Accept-Encoding 字段
    gzip_vary on;
    # 让 nginx 也动态压缩反向代理的内容
    gzip_proxied expired no-cache no-store private auth;

    # 启用 brotli_static 以提供预压缩的 .br 文件
    # 需要 ngx_brotli 模块: https://github.com/google/ngx_brotli
    # 如果你使用 1Panel 面板:
    #     可前往 /websites 页面,点击设置->模块->启用 ngx_brotli->构建,即可启用。
    brotli_static on;

    # 如果找不到静态文件则回退到动态压缩
    brotli on;
    brotli_types application/atom+xml application/javascript application/json application/vnd.api+json application/rss+xml application/vnd.ms-fontobject application/x-font-opentype application/x-font-truetype application/x-font-ttf application/x-javascript application/xhtml+xml application/xml font/eot font/opentype font/otf font/truetype image/svg+xml image/vnd.microsoft.icon image/x-icon image/x-win-bitmap text/css text/javascript text/plain text/xml;

    # 启用 zstd_static 以提供预压缩的 .zst 文件
    # 需要 zstd-nginx-module 模块: https://github.com/tokers/zstd-nginx-module
    zstd_static on;

    # 如果找不到静态文件则回退到动态压缩
    zstd on;
    zstd_types application/atom+xml application/javascript application/json application/vnd.api+json application/rss+xml application/vnd.ms-fontobject application/x-font-opentype application/x-font-truetype application/x-font-ttf application/x-javascript application/xhtml+xml application/xml font/eot font/opentype font/otf font/truetype image/svg+xml image/vnd.microsoft.icon image/x-icon image/x-win-bitmap text/css text/javascript text/plain text/xml;

    server {
        # 其他配置
        listen 80;
        server_name example.com;
        root /var/www/html;

        location / {
            try_files $uri $uri/ /index.html;
        }
    }
}

在 Apache 上使用

# 启用 mod_deflate 以实现回退动态压缩
<IfModule mod_deflate.c>
    AddOutputFilterByType DEFLATE application/atom+xml application/javascript application/json application/vnd.api+json application/rss+xml application/vnd.ms-fontobject application/x-font-opentype application/x-font-truetype application/x-font-ttf application/x-javascript application/xhtml+xml application/xml font/eot font/opentype font/otf font/truetype image/svg+xml image/vnd.microsoft.icon image/x-icon image/x-win-bitmap text/css text/javascript text/plain text/xml
</IfModule>

# 提供预压缩文件
<IfModule mod_rewrite.c>
    RewriteEngine On

    # 如果存在 .zst 文件且客户端支持 zstd,则提供该文件
    RewriteCond %{HTTP:Accept-Encoding} zstd
    RewriteCond %{REQUEST_FILENAME}.zst -f
    RewriteRule ^(.*)$ $1.zst [L]

    # 如果存在 .br 文件且客户端支持 brotli,则提供该文件
    RewriteCond %{HTTP:Accept-Encoding} br
    RewriteCond %{REQUEST_FILENAME}.br -f
    RewriteRule ^(.*)$ $1.br [L]

    # 如果存在 .gz 文件且客户端支持 gzip,则提供该文件
    RewriteCond %{HTTP:Accept-Encoding} gzip
    RewriteCond %{REQUEST_FILENAME}.gz -f
    RewriteRule ^(.*)$ $1.gz [L]
</IfModule>

# 设置正确的 content-type 和 encoding headers
<FilesMatch "\.js\.gz$">
    Header set Content-Type "application/javascript"
    Header set Content-Encoding "gzip"
</FilesMatch>

<FilesMatch "\.css\.gz$">
    Header set Content-Type "text/css"
    Header set Content-Encoding "gzip"
</FilesMatch>

<FilesMatch "\.js\.br$">
    Header set Content-Type "application/javascript"
    Header set Content-Encoding "br"
</FilesMatch>

<FilesMatch "\.css\.br$">
    Header set Content-Type "text/css"
    Header set Content-Encoding "br"
</FilesMatch>

<FilesMatch "\.js\.zst$">
    Header set Content-Type "application/javascript"
    Header set Content-Encoding "zstd"
</FilesMatch>

<FilesMatch "\.css\.zst$">
    Header set Content-Type "text/css"
    Header set Content-Encoding "zstd"
</FilesMatch>

在 Halo CMS 上使用

经检查,Halo CMS v2.22.14 会自动采用 .br 文件,但不会自动采用 .zst 文件。

如何确定生效

  1. 确定你的浏览器支持所选择的协议:gzipbrotlizstd
  2. 打开浏览器的开发者工具。
  3. 选择“网络”(Network)分页。
  4. 刷新网页,点击你要检查的文件(比如 .js 后缀的文件)
  5. 查看标头(Headers),找到 Content-Encoding,如果是 br, gzip, zstd,说明正确采用了对应的压缩算法传输。

注意:在 114 版本 Edge 上测试,发现在 https 站点/127.0.0.1 的情况下 Accept-Encodinggzip, deflate, br, zstd。而在 http 站点上 Accept-Encodinggzip, deflate
结论:想要使用 brotli 和 zstandard 算法的预压缩文件,需要先给站点配置 https。

后记

欢迎留言交流。

鸣谢

文章分享的 MIME 配置参考了 google/ngx_brotli,再反向推断出后文件后缀。
文章分享的 Apache 和 nginx 配置是由 nonzzz/vite-plugin-compression 修改而来。

相关文章分享


0
上一篇 CVE-2025-70886 Proof of Concept (PoC) | A Script to Crash Halo CMS Comment Backend