NextJS移动端JS资源未Gzip压缩:请求头来源与修复方案咨询
嘿,这个问题我之前也碰到过类似的,来给你梳理清楚:
首先明确:Accept-Encoding 请求头是谁加的?
完全是浏览器自动添加的,NextJS 和 Webpack 都不会干预浏览器发送这个请求头。浏览器会根据自身设置、网络环境等,决定是否带上这个头来告诉服务器:“我支持压缩格式,麻烦给我发压缩后的资源”。桌面端和移动端的差异,本质是不同环境下浏览器的行为不同。
为什么移动端没带这个头?
大概率是这几个原因:
- 移动端浏览器开启了数据节省模式(比如 Chrome 的 Data Saver、Safari 的 Low Data Mode),这类模式会主动移除或修改 Accept-Encoding 头,避免压缩带来的额外CPU开销(毕竟移动端性能相对有限);
- 部分小众移动端浏览器的默认设置就是不发送这个头;
- 如果是用模拟器测试,可能模拟器的网络配置模拟了低带宽/省流量场景,导致浏览器屏蔽了该头。
怎么修复这个问题?
既然没法给 <script> 标签加请求头,我们可以从服务器端强制压缩资源,或者排查浏览器设置:
方案1:让 NextJS 强制返回压缩后的 JS 资源
即使请求头不带 Accept-Encoding,也主动返回 Gzip 压缩后的内容。可以通过两种方式实现:
方式A:用 Webpack 预压缩资源
提前生成 Gzip 格式的 JS 文件,让 NextJS 优先返回这些预压缩文件:
- 安装压缩插件:
npm install compression-webpack-plugin --save-dev
- 修改
next.config.js:
const CompressionPlugin = require('compression-webpack-plugin'); /** @type {import('next').NextConfig} */ const nextConfig = { compress: true, // 确保开启服务器压缩 webpack: (config) => { config.plugins.push( new CompressionPlugin({ algorithm: 'gzip', test: /\.(js|css|html|svg)$/, // 匹配需要压缩的文件类型 threshold: 8192, // 只有大于8KB的文件才压缩 minRatio: 0.8, // 压缩率低于0.8的才保留压缩文件 }) ); return config; }, }; module.exports = nextConfig;
构建生产版本后,NextJS 的服务器会自动检测并返回预生成的 .gz 文件,无需依赖请求头(不过部分场景下还是需要浏览器支持解压缩,但现在主流移动端浏览器都支持)。
方式B:用 NextJS 中间件强制压缩响应
创建 middleware.ts(或 .js),拦截 JS 请求并主动压缩:
import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; import zlib from 'zlib'; import { promisify } from 'util'; const gzip = promisify(zlib.gzip); export async function middleware(request: NextRequest) { // 只处理静态JS chunk if (request.nextUrl.pathname.match(/\/_next\/static\/.*\.js$/)) { const originalResponse = await fetch(request.url); const body = await originalResponse.text(); const compressedBody = await gzip(body); return new NextResponse(compressedBody, { status: originalResponse.status, headers: { ...originalResponse.headers, 'Content-Encoding': 'gzip', 'Content-Length': compressedBody.length.toString(), 'Vary': 'Accept-Encoding', // 保留Vary头,避免缓存问题 }, }); } return NextResponse.next(); }
这个中间件会自动处理所有 NextJS 生成的静态 JS 资源,不管请求头有没有 Accept-Encoding,都返回 Gzip 压缩后的内容。
方案2:排查移动端浏览器设置
如果是测试时的临时问题,可以检查移动端浏览器是否开启了省流量模式,关闭后浏览器应该会自动带上 Accept-Encoding 头,服务器也就会返回压缩后的资源了。
方案3:确认 Vary 头配置
确保 NextJS 返回的响应头里包含 Vary: Accept-Encoding,这样如果有缓存层(比如 CDN),会根据请求头的不同存储不同的压缩/未压缩版本,避免移动端拿到缓存的未压缩资源。
内容的提问来源于stack exchange,提问作者darKnight

