最近在使用 Vite 开发一个 Vue3 项目时,遇到了一个颇为棘手的网络错误。项目配置了代理 ( serv er.proxy ) 将特定前缀(比如 /api )的请求转发到后端服务。大部分接口工作正常,但部分接口在浏览器控制台会抛出 ERR_ CONTENT_DECODING_FAILED 错误。这个错误通常意味着浏览器接收到了经过压缩(如 gzip, br)的响应内容,但无法正确解码。
排查过程
-
检查后端服务: 首先确认后端服务本身是正常的,直接访问后端接口 URL(不通过 Vite 代理)可以成功返回预期的 JSON 数据或其它内容,且响应头 Content-Encoding 显示后端确实返回了压缩内容(如 gzip )。
-
检查 Vite 代理配置: 基础的代理配置看起来没有问题:
// vite.config.jsexport default defineConfig({ server: { proxy: { '/api': { target: ' your-backend-server.com', // 后端地址 changeOrigin: true, // 通常建议开启 rewrite: (path) => path.replace(/^/api/, ''), // 可选,重写路径 // ... 其他配置 ... } } }, // ... 其他配置 ...});
JavaScript
配置了 changeOrigin: true 确保请求头中的 Host 和 Origin 被正确修改以应对跨域问题。
- 对比请求差异: 使用浏览器开发者工具对比了通过 Vite 代理的请求和直接请求后端的请求/响应头信息。发现关键差异在于 Accept-Encoding 请求头:
-
直接请求后端: 浏览器发送的 Accept-Encoding 通常包含 gzip, deflate, br 等,表明浏览器可以接受这些压缩格式。后端据此返回压缩内容并设置 Content-Encoding: gzip 。
-
通过 Vite 代理请求: Vite 开发服务器在转发请求给后端时,默认也会带上 Accept-Encoding: gzip, deflate, br (或类似)的请求头。后端同样识别到这个头,并返回了压缩内容 ( Content-Encoding: gzip )。
- 问题定位: 问题出在 Vite 开发服务器对代理响应的处理上。当后端返回压缩内容时:
-
Vite 开发服务器(基于 http-proxy-middleware )接收到了这个压缩的响应体。
-
它试图将这个压缩的响应体原样转发给浏览器。
-
然而,浏览器在接收到这个响应时,发现响应头 Content-Encoding: gzip 存在,表明内容需要解压。
-
浏览器尝试解压这个响应体,但失败了,导致 ERR_CONTENT_DECODING_FAILED 错误。
核心原因
Vite 代理默认行为是"透明"转发请求和响应。它不会主动解压后端返回的压缩内容,而是直接将其传递给前端浏览器。浏览器看到 Content-Encoding 头,就会尝试解压,但如果这个压缩流在传输或处理过程中出现任何不兼容或损坏(即使后端压缩本身是正确的,代理的传递过程也可能引入微妙的不兼容),或者浏览器对特定压缩算法的实现有细微差异,解压就可能失败。
解决方案:强制后端返回未压缩内容
既然问题源于浏览器无法正确处理代理转发的压缩响应,最直接的思路就是阻止后端返回压缩内容。我们可以在代理请求中明确告诉后端:"我不接受任何压缩格式,请给我原始(identity)内容"。
这就是通过设置 headers 选项中的 Accept-Encoding 来实现的:
css
// vite.config.jsexport default defineConfig({ server: { proxy: { '/api': { target: ' https://your-backend-server.com', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, ''), // 可选 // 关键解决方案:添加 headers 配置 headers: { 'Accept-Encoding': 'identity', // 明确要求后端不要压缩响应体 }, } } }, // ... 其他配置 ...});
TypeScript
解释
-
headers 选项允许我们在 Vite 代理将请求转发给目标服务器(后端)之前,修改或添加请求头。
-
设置 'Accept-Encoding': 'identity' :
-
Accept-Encoding 是 HTTP 请求头,用于告知服务器客户端能够理解的内容编码(压缩)方式。
-
identity 是一个特殊值,表示"不压缩"、"无编码"、"原样"。它明确告诉服务器:"请直接返回原始数据,不要进行任何压缩"。
-
效果: 后端服务器收到这个请求头后,知道客户端(此时是 Vite 代理服务器,它代表浏览器)不接受压缩,因此会返回未经压缩的原始响应体,并且响应头中通常不会包含 Content-Encoding ,或者其值为 identity 。
-
结果: Vite 代理将这个未压缩的响应体转发给浏览器。浏览器没有看到 Content-Encoding 头,或者看到 identity ,就知道内容不需要解压,直接使用即可。 ERR_CONTENT_DECODING_FAILED 错误消失。
总结与启示
-
问题本质: ERR_CONTENT_DECODING_FAILED 在 Vite 代理场景下,通常是由于代理直接转发了后端的压缩响应,而浏览器解压该响应时失败。
-
解决方案: 在 Vite 的代理配置 ( server.proxy[xxx].headers ) 中设置 'Accept-Encoding': 'identity' ,强制要求后端返回未压缩的原始内容。这消除了浏览器解压环节,从而避免了解压失败的错误。
-
权衡: 此方案的代价是牺牲了网络传输的压缩效率。未压缩的内容体积更大,可能会略微增加加载时间。但在开发环境或部分特定接口遇到此问题时,稳定性优先于那一点传输效率通常是更合理的选择。对于生产环境,静态资源应使用构建时预压缩(如 vite-plugin-compression ),并由服务器(如 Nginx)根据请求头 Accept-Encoding 动态提供正确的压缩版本或原始版本给浏览器。
-
排查技巧: 遇到代理相关问题时,仔细对比代理前后请求/响应头的差异是至关重要的第一步。开发者工具的网络面板是解决此类问题的利器。