在项目开发中遇到了一个诡异的问题✅ 后端 API 完全正常✅ CORS 配置正确无误✅ Cookie 中有有效 token✅ 手动测试都能成功❌但前端页面请求就是返回 HTTP Status 0// 前端控制台报错 API 响应不是有效的 JSON: http://localhost:9421/api/v1/media?page1per_page20 , status: 0 排查过程第一步验证后端直接在浏览器访问 API成功返回数据说明后端没问题。curl http://localhost:9421/api/v1/media/?page1per_page20 # ✅ 返回正常 JSON 数据第二步检查 CORS测试 OPTIONS 预检请求完美通过// CORS 头都正确 Access-Control-Allow-Origin: http://localhost:3000 Access-Control-Allow-Credentials: true第三步验证认证Cookie 中有有效的 JWT token手动发起请求测试// ✅ 成功 fetch(http://localhost:9421/api/v1/media/?page1per_page20, { credentials: include, headers: { Authorization: Bearer xxx } }) // Status: 200 ✓第四步排除插件干扰为了彻底排除浏览器插件如广告拦截器、代理扩展等对请求的干扰我们在无痕模式下禁用了所有插件和脚本再次发起请求。// 在无痕模式下所有扩展默认禁用 fetch(http://localhost:9421/api/v1/media/?page1per_page20, { credentials: include }) // 结果仍然返回 status 0确认问题与插件无关继续深入调试。第五步发现问题继续深入调试发现了一个关键信息Response { status: 0, type: opaqueredirect, // ← 关键 ok: false, headers: {} // 空的 }opaque-redirect是什么鬼 问题根源原来问题出在这行代码上// ❌ 错误的配置 const response await fetch(url, { method: GET, credentials: include, redirect: manual // ← 就是这个 });什么是redirect: manualFetch API 提供了三种重定向模式模式行为使用场景follow默认自动跟随重定向大多数情况manual不跟随重定向返回 opaque-redirect需要手动处理重定向 URLerror遇到重定向直接报错严格模式为什么会返回 Status 0前端请求发送后后端返回3xx 重定向响应比如重定向到登录页因为设置了redirect: manual浏览器不会跟随重定向浏览器返回一个opaque-redirect 类型的响应这个响应的status 0headers {}body 为空尝试解析 JSON 时就会报错Unexpected end of input为什么会有redirect: manual这个选项既然这个选项这么容易出问题那它存在的意义是什么根据 Fetch 规范redirect: manual的设计初衷主要是为了浏览器内部导航和Service Worker等特定场景。在这些场景下开发者需要完全控制重定向的处理流程。但对于普通的前端应用代码你几乎永远不需要用到它。换句话说除非你在写 Service Worker 或做非常底层的网络控制否则永远不要碰redirect: manual✅ 解决方案方案 1移除redirect: manual推荐// ✅ 正确的做法 const response await fetch(url, { method: GET, credentials: include // 不设置 redirect使用默认的 follow });让浏览器自动处理重定向99% 的情况都应该这样做方案 2如果你确实需要处理重定向但不用manual// 正确的重定向处理方式在响应中判断状态码 const response await fetch(url, { method: GET, credentials: include }); if (response.redirected) { // 重定向发生了response.url 已经是最终 URL console.log(重定向到了:, response.url); } if (response.status 302) { // 也可以手动获取重定向地址但不推荐手动跳转 const location response.headers.get(Location); }注意使用默认的follow模式浏览器会自动跟随重定向你拿到的是最终响应。response.redirected属性可以告诉你是否发生过重定向。 最佳实践✅ DO推荐做法// 1. 使用默认配置 fetch(/api/data, { method: GET, credentials: include }); // 2. 如果需要处理认证失败在响应中判断 fetch(/api/data, { credentials: include }) .then(response { if (response.status 401) { // 未认证跳转到登录页 window.location.href /login; } return response.json(); }); // 3. 检查是否发生了重定向 const response await fetch(/api/data); if (response.redirected) { console.log(请求被重定向到了:, response.url); }❌ DONT避免这样做// 1. 不要随意设置 redirect: manual fetch(/api/data, { redirect: manual // ❌ 除非你非常清楚后果 }); // 2. 不要这样手动处理重定向这是错误的示范 fetch(/api/data, { redirect: manual }).then(response { // 这段代码几乎永远不会按预期工作 if (response.type opaqueredirect) { window.location.href response.headers.get(Location); // headers 是空的 } }); 扩展还有哪些情况会导致 Status 0除了redirect: manual导致的opaque-redirect外status 0 还可能由以下原因引起原因说明网络连接问题网络断开、WiFi 信号差、飞行模式等跨域请求被阻止CORS 配置不当OPTIONS 预检失败请求被取消页面刷新、导航导致请求被中断协议混合问题HTTPS 页面请求 HTTP 资源被浏览器阻止DNS 解析失败域名无法解析所以当你遇到 status 0 时可以先按以下步骤快速排查打开浏览器开发者工具 → Network 面板查看请求是否真的发出了状态列显示什么如果有请求但状态为(failed)或(canceled)检查网络和 CORS如果请求显示为302但代码里报 status 0极大概率就是本文的问题 一个真实案例有开发者在微信公众号 H5 开发中遇到过类似问题iOS 10 系统下设置了自定义 header 后所有请求都返回 status 0。排查发现是自定义 header 触发了 CORS 预检而 iOS 10 对 CORS 预检的兼容性有问题后端返回的Access-Control-Allow-Headers: *不被识别导致真正的请求没有被发送。解决方法是将*改为明确列出的 header 名称Access-Control-Allow-Headers: content-type, auth这个案例告诉我们status 0 是浏览器层面的“请求没发出去”或“响应被拦截”通常不是后端业务错误要从前端/网络层面找原因。 学到的教训不要过度设计- 浏览器的默认行为通常是最好的理解 HTTP 状态码- Status 0 不代表服务器错误而是网络层面的问题善用调试工具- Network 面板能看到请求的详细信息了解 Fetch API- 各种参数的含义和使用场景特别是redirect这个容易被忽视的选项遇到 status 0先检查网络和配置再怀疑后端- 因为请求根本没到服务器 相关资源MDN - Fetch APIMDN - Request.redirectHTTP 重定向详解Fetch 规范 - opaque-redirect 响应类型
前端请求返回 HTTP Status 0?这个坑 90% 的开发者都会踩!
发布时间:2026/7/2 5:46:53
在项目开发中遇到了一个诡异的问题✅ 后端 API 完全正常✅ CORS 配置正确无误✅ Cookie 中有有效 token✅ 手动测试都能成功❌但前端页面请求就是返回 HTTP Status 0// 前端控制台报错 API 响应不是有效的 JSON: http://localhost:9421/api/v1/media?page1per_page20 , status: 0 排查过程第一步验证后端直接在浏览器访问 API成功返回数据说明后端没问题。curl http://localhost:9421/api/v1/media/?page1per_page20 # ✅ 返回正常 JSON 数据第二步检查 CORS测试 OPTIONS 预检请求完美通过// CORS 头都正确 Access-Control-Allow-Origin: http://localhost:3000 Access-Control-Allow-Credentials: true第三步验证认证Cookie 中有有效的 JWT token手动发起请求测试// ✅ 成功 fetch(http://localhost:9421/api/v1/media/?page1per_page20, { credentials: include, headers: { Authorization: Bearer xxx } }) // Status: 200 ✓第四步排除插件干扰为了彻底排除浏览器插件如广告拦截器、代理扩展等对请求的干扰我们在无痕模式下禁用了所有插件和脚本再次发起请求。// 在无痕模式下所有扩展默认禁用 fetch(http://localhost:9421/api/v1/media/?page1per_page20, { credentials: include }) // 结果仍然返回 status 0确认问题与插件无关继续深入调试。第五步发现问题继续深入调试发现了一个关键信息Response { status: 0, type: opaqueredirect, // ← 关键 ok: false, headers: {} // 空的 }opaque-redirect是什么鬼 问题根源原来问题出在这行代码上// ❌ 错误的配置 const response await fetch(url, { method: GET, credentials: include, redirect: manual // ← 就是这个 });什么是redirect: manualFetch API 提供了三种重定向模式模式行为使用场景follow默认自动跟随重定向大多数情况manual不跟随重定向返回 opaque-redirect需要手动处理重定向 URLerror遇到重定向直接报错严格模式为什么会返回 Status 0前端请求发送后后端返回3xx 重定向响应比如重定向到登录页因为设置了redirect: manual浏览器不会跟随重定向浏览器返回一个opaque-redirect 类型的响应这个响应的status 0headers {}body 为空尝试解析 JSON 时就会报错Unexpected end of input为什么会有redirect: manual这个选项既然这个选项这么容易出问题那它存在的意义是什么根据 Fetch 规范redirect: manual的设计初衷主要是为了浏览器内部导航和Service Worker等特定场景。在这些场景下开发者需要完全控制重定向的处理流程。但对于普通的前端应用代码你几乎永远不需要用到它。换句话说除非你在写 Service Worker 或做非常底层的网络控制否则永远不要碰redirect: manual✅ 解决方案方案 1移除redirect: manual推荐// ✅ 正确的做法 const response await fetch(url, { method: GET, credentials: include // 不设置 redirect使用默认的 follow });让浏览器自动处理重定向99% 的情况都应该这样做方案 2如果你确实需要处理重定向但不用manual// 正确的重定向处理方式在响应中判断状态码 const response await fetch(url, { method: GET, credentials: include }); if (response.redirected) { // 重定向发生了response.url 已经是最终 URL console.log(重定向到了:, response.url); } if (response.status 302) { // 也可以手动获取重定向地址但不推荐手动跳转 const location response.headers.get(Location); }注意使用默认的follow模式浏览器会自动跟随重定向你拿到的是最终响应。response.redirected属性可以告诉你是否发生过重定向。 最佳实践✅ DO推荐做法// 1. 使用默认配置 fetch(/api/data, { method: GET, credentials: include }); // 2. 如果需要处理认证失败在响应中判断 fetch(/api/data, { credentials: include }) .then(response { if (response.status 401) { // 未认证跳转到登录页 window.location.href /login; } return response.json(); }); // 3. 检查是否发生了重定向 const response await fetch(/api/data); if (response.redirected) { console.log(请求被重定向到了:, response.url); }❌ DONT避免这样做// 1. 不要随意设置 redirect: manual fetch(/api/data, { redirect: manual // ❌ 除非你非常清楚后果 }); // 2. 不要这样手动处理重定向这是错误的示范 fetch(/api/data, { redirect: manual }).then(response { // 这段代码几乎永远不会按预期工作 if (response.type opaqueredirect) { window.location.href response.headers.get(Location); // headers 是空的 } }); 扩展还有哪些情况会导致 Status 0除了redirect: manual导致的opaque-redirect外status 0 还可能由以下原因引起原因说明网络连接问题网络断开、WiFi 信号差、飞行模式等跨域请求被阻止CORS 配置不当OPTIONS 预检失败请求被取消页面刷新、导航导致请求被中断协议混合问题HTTPS 页面请求 HTTP 资源被浏览器阻止DNS 解析失败域名无法解析所以当你遇到 status 0 时可以先按以下步骤快速排查打开浏览器开发者工具 → Network 面板查看请求是否真的发出了状态列显示什么如果有请求但状态为(failed)或(canceled)检查网络和 CORS如果请求显示为302但代码里报 status 0极大概率就是本文的问题 一个真实案例有开发者在微信公众号 H5 开发中遇到过类似问题iOS 10 系统下设置了自定义 header 后所有请求都返回 status 0。排查发现是自定义 header 触发了 CORS 预检而 iOS 10 对 CORS 预检的兼容性有问题后端返回的Access-Control-Allow-Headers: *不被识别导致真正的请求没有被发送。解决方法是将*改为明确列出的 header 名称Access-Control-Allow-Headers: content-type, auth这个案例告诉我们status 0 是浏览器层面的“请求没发出去”或“响应被拦截”通常不是后端业务错误要从前端/网络层面找原因。 学到的教训不要过度设计- 浏览器的默认行为通常是最好的理解 HTTP 状态码- Status 0 不代表服务器错误而是网络层面的问题善用调试工具- Network 面板能看到请求的详细信息了解 Fetch API- 各种参数的含义和使用场景特别是redirect这个容易被忽视的选项遇到 status 0先检查网络和配置再怀疑后端- 因为请求根本没到服务器 相关资源MDN - Fetch APIMDN - Request.redirectHTTP 重定向详解Fetch 规范 - opaque-redirect 响应类型