资讯动态

前端跨域问题实战指南:CORS、代理与JSONP全解析

发布时间:2026/10/17 4:54:46 来源:尧图企业网站定制
1. 项目概述跨域不是“错误”而是浏览器的安全守门员“ajax解决跨域问题POST、GET以及如何判断跨域”——这个标题背后藏着成千上万前端开发者在调试接口时皱起的眉头、控制台里反复刷屏的红色报错还有后端同事一句轻描淡写的“你配个CORS不就好了”。但现实是很多开发者直到线上出问题才真正搞懂为什么本地能跑通一部署就404为什么用Postman能拿到数据换到页面里就卡死为什么改了请求头还是被拦而加个?callbackxxx反而通了。这不是玄学是浏览器基于同源策略Same-Origin Policy筑起的一道默认防火墙。它不针对你也不歧视POST或GET它只认一个铁律协议、域名、端口三者完全一致才算“自己人”。比如https://a.com:8080访问https://b.com/api/user——协议相同https但域名不同a.com vs b.com立刻触发跨域拦截而http://localhost:3000调http://localhost:8000——协议和域名都一样但端口不同3000 vs 8000同样算跨域。很多人误以为“跨域后端没开权限”其实第一步得先确认这个请求到底有没有触发跨域机制判断依据不在后端日志里而在浏览器开发者工具的Network面板——看请求是否发出、响应状态码是否为0、Preview/Response标签页是否为空再结合Console里的Access-Control-Allow-Origin相关报错。本文不讲抽象理论只聚焦一线实操从肉眼识别跨域现象开始到用CORS、JSONP、代理等真实方案逐个击破POST/GET场景每一步都附带Chrome DevTools截图级的操作指引、Nginx反向代理配置模板、Spring Boot和Express的CORS代码片段以及我踩过最深的三个坑——比如预检请求preflight被悄悄拦截却无任何报错提示或者携带cookie时Access-Control-Allow-Origin不能写*这种反直觉限制。适合刚接触接口联调的新人也适合需要快速排查生产环境问题的资深同学。2. 跨域本质与精准判断别让浏览器“背锅”先看清它到底拦了什么2.1 同源策略不是bug是Web安全的基石同源策略Same-Origin Policy是浏览器最底层的安全模型之一它的存在不是为了给开发者添堵而是为了防止恶意网站偷偷读取你另一个标签页里的银行账户信息。想象一下你同时开着网银https://bank.com和一个钓鱼网站https://evil-site.com。如果没有同源策略后者只需一段JS就能发起fetch(https://bank.com/api/balance)并把返回的余额数据发回自己的服务器——你的资产瞬间暴露。同源策略强制规定只有当两个URL的协议scheme、主机名host、端口号port全部相同时才允许前端脚本读取对方的响应内容。注意这里的关键是“读取响应”而不是“发送请求”。浏览器会放行绝大多数跨域请求的发送比如GET请求直接发出去但会在收到响应后检查响应头如果发现不满足同源要求就直接丢弃响应体并在控制台抛出No Access-Control-Allow-Origin header is present on the requested resource这类错误。这意味着跨域拦截发生在响应阶段而非请求阶段。这也是为什么你在后端日志里可能看到请求已到达说明网络层通畅但前端却收不到任何数据——浏览器在门口就把快递拒收了连拆封的机会都不给。2.2 三步法精准判断是真跨域还是其他问题很多同学一看到控制台报错就下意识认为“肯定是跨域”结果折腾半天CORS配置最后发现是接口地址写错了、HTTPS证书失效或者后端服务根本没起来。判断跨域必须回归证据链我总结了一套三步验证法已在多个项目中反复验证第一步看Network面板的请求状态打开Chrome DevToolsF12切到Network标签页勾选“Preserve log”防止页面跳转清空记录然后触发你的AJAX请求。重点观察这一行如果请求根本没有出现在列表里说明JS代码根本没执行到发送请求这一步可能是语法错误、条件判断失败或者fetch/axios实例初始化异常如果请求出现了但Status显示为(canceled)或(failed)且Timing标签页里Request时间极短1ms大概率是JS主动中止如abortController.abort()或网络中断如果请求出现了Status是200或404等具体状态码且Size列有数值如2.3 KB说明请求已成功发出并收到响应此时基本排除跨域问题应转向排查后端逻辑或数据格式最关键的信号Status显示为0Size为(from disk cache)或(from memory cache)或者干脆是空的——这99%是跨域拦截。因为浏览器压根没把响应体交给你所以无法计算真实大小只能标记为0。第二步查Console报错的精确文本跨域报错有非常典型的模式绝不是笼统的“网络错误”Access to fetch at https://api.example.com/data from origin http://localhost:3000 has been blocked by CORS policy: No Access-Control-Allow-Origin header is present on the requested resource.→ 这是最常见的CORS缺失报错说明后端响应头里没加Access-Control-Allow-OriginAccess to fetch at https://api.example.com/data from origin http://localhost:3000 has been blocked by CORS policy: Response to preflight request doesnt pass access control check: No Access-Control-Allow-Origin header is present on the requested resource.→ 这个报错里多了preflight request意味着你发的是复杂请求如带自定义header、Content-Type为application/json的POST浏览器先发了个OPTIONS预检但预检响应头里缺CORS字段The value of the Access-Control-Allow-Origin header in the response must not be the wildcard * when the requests credentials mode is include.→ 这个报错直指核心矛盾你前端设置了credentials: include要带cookie但后端CORS头却写了Access-Control-Allow-Origin: *这是浏览器明令禁止的必须指定具体域名。第三步抓包验证——绕过浏览器看真实网络流如果以上两步还存疑终极手段是用抓包工具如Charles或Fiddler或curl命令模拟同样的请求发给后端curl -X GET https://api.example.com/data \ -H Origin: http://localhost:3000 \ -H Content-Type: application/json \ -v加上-v参数可以看到完整的HTTP请求头和响应头。如果curl能拿到完整响应包括Access-Control-Allow-Origin等头而浏览器不行那100%是跨域问题如果curl也报错如Connection refused那就是网络或后端服务问题和跨域无关。提示判断跨域时务必关闭所有浏览器插件尤其是广告屏蔽、隐私保护类它们有时会注入额外header干扰CORS检测。我曾在一个项目里耗时两天排查最后发现是某款“隐私卫士”插件自动添加了Sec-Fetch-Site: cross-site头导致后端Nginx规则误判为恶意请求而拒绝响应。2.3 GET与POST跨域的差异为什么POST更容易“中招”很多新手困惑为什么同样的域名GET请求好好的换成POST就报跨域根源在于浏览器对“简单请求”和“复杂请求”的区分。根据W3C标准满足以下所有条件的请求才是简单请求Simple Request浏览器会直接发送不触发预检请求方法是HEAD、GET或POSTContent-Type值仅限于text/plain、multipart/form-data、application/x-www-form-urlencoded不包含自定义header如X-Auth-TokenAccept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width这些“CORS-safelisted request-header”除外。一旦不满足任一条件就是复杂请求Complex Request浏览器必须先发一个OPTIONS请求即预检请求去询问后端“我待会要用POSTJSON发数据你允许吗”——这个预检请求的响应头里必须包含Access-Control-Allow-Origin、Access-Control-Allow-Methods如POST, GET、Access-Control-Allow-Headers如Content-Type, X-Requested-With等字段否则浏览器就拒绝发送真正的POST请求。这就是为什么用axios.get(/api/data)通常很顺利简单GET但axios.post(/api/login, {user: a, pwd: b})却卡在OPTIONSPOSTJSON是复杂请求而axios.post(/api/form, new FormData(formEl))又能成功POSTmultipart/form-data是简单请求。实测下来超过70%的POST跨域问题根源都在预检请求被后端忽略或配置不全。后端同学常犯的错误是只给业务接口加CORS头却忘了给OPTIONS路由单独配置或者Nginx配置里if ($request_method OPTIONS)分支漏掉了关键header。3. 四大主流解决方案深度解析从CORS到代理选对方案比硬刚更重要3.1 CORS方案后端配合的“正规军”但细节决定成败CORSCross-Origin Resource Sharing是W3C制定的标准方案也是现代Web应用解决跨域的首选。它的核心思想很朴素浏览器负责拦截后端负责授权。当浏览器发现跨域请求时会检查响应头里是否有合法的CORS字段后端只要在响应中正确设置这些字段浏览器就会放行。但“正确设置”四个字藏着大量实操陷阱。基础配置适用于简单GET/POST以Node.js Express为例最简配置如下app.use((req, res, next) { res.header(Access-Control-Allow-Origin, http://localhost:3000); // 必须指定具体域名不能用* res.header(Access-Control-Allow-Methods, GET, POST, PUT, DELETE, OPTIONS); res.header(Access-Control-Allow-Headers, Content-Type, Authorization, X-Requested-With); res.header(Access-Control-Allow-Credentials, true); // 若需携带cookie此项必开 if (req.method OPTIONS) { res.sendStatus(200); // 预检请求直接返回200不走后续中间件 } else { next(); } });这段代码看似简单但每个字段都有讲究Access-Control-Allow-Origin开发环境可写http://localhost:3000但生产环境绝不能写死。我见过太多项目上线后因用户从https://www.a.com和https://a.com两个域名访问导致后者被CORS拦截。正确做法是动态匹配Origin头const allowedOrigins [https://a.com, https://www.a.com]; const origin req.headers.origin; if (allowedOrigins.includes(origin)) { res.header(Access-Control-Allow-Origin, origin); }Access-Control-Allow-Credentials设为true时Access-Control-Allow-Origin绝对不能是*否则浏览器直接报错。这是硬性规定没有例外。Access-Control-Allow-Headers必须包含前端实际发送的所有非简单header。比如你用axios设置了headers: {Authorization: Bearer xxx}这里就必须加上Authorization否则预检失败。进阶配置应对复杂场景在微服务架构中API网关常统一处理CORS此时需注意网关转发请求时可能覆盖或丢失原始Origin头需在Nginx中显式透传location /api/ { proxy_pass https://backend-service/; proxy_set_header Origin $http_origin; # 关键透传Origin proxy_set_header Host $host; }对于文件上传POSTmultipart/form-data某些老旧后端框架如Java Servlet 3.0前不支持直接读取OPTIONS请求体需在网关层拦截OPTIONS并返回空响应避免请求落到业务层报错。实操心得我在某电商项目中遇到一个诡异问题——CORS配置完全正确但iOS Safari仍报跨域。排查发现是Safari对Access-Control-Allow-Origin的校验更严格要求header必须在响应的第一行出现而我们的后端框架因日志中间件插入了额外header导致CORS头被挤到了后面。解决方案是在CORS中间件里用res.writeHead()手动写入状态码和header确保CORS头最先输出。3.2 JSONP方案古董级“曲线救国”仅限GET且安全性堪忧JSONPJSON with Padding是CORS出现前的“土办法”原理极其巧妙利用script标签不受同源策略限制的特性。前端动态创建一个script srchttps://api.example.com/data?callbackhandleData后端收到请求后不返回纯JSON而是返回一段JS代码handleData({name: Alice, age: 25})。浏览器执行这段代码就相当于调用了前端定义的handleData函数数据自然就拿到了。实现步骤前端function jsonp(url, callbackName, callback) { const script document.createElement(script); window[callbackName] callback; // 将回调挂到全局 script.src ${url}?callback${callbackName}; document.head.appendChild(script); } // 使用 jsonp(https://api.example.com/users, getUserCallback, (data) { console.log(获取到用户数据:, data); });后端响应Node.js示例app.get(/users, (req, res) { const callback req.query.callback; const data JSON.stringify([{id: 1, name: Alice}]); res.set(Content-Type, application/javascript); res.send(${callback}(${data});); });致命缺陷与适用场景仅支持GET因为script只能发GET请求无错误处理script加载失败404、超时不会触发onerror只能靠setTimeout轮询安全性风险后端返回的JS代码会被无条件执行若被注入恶意脚本如callbackalert(1)前端就遭殃。因此JSONP必须配合严格的callback参数白名单const validCallbacks [getUserCallback, getProductCallback]; if (!validCallbacks.includes(req.query.callback)) { return res.status(400).send(Invalid callback); }现代替代方案除非维护超老系统IE6-8否则强烈建议放弃JSONP。Vue 3的createApp、React 18的createRoot等新API已彻底移除对JSONP的内置支持。3.3 开发代理方案前端工程化“障眼法”零后端修改开发代理Dev Proxy是前端工程师的“免死金牌”它不改变后端任何代码纯粹在本地构建工具如Webpack/Vite中把前端请求“伪装”成同源请求。原理是前端代码里请求/api/user构建工具捕获该请求将其转发到真实后端https://api.example.com/user再把响应原样返回给前端。对浏览器而言它只和http://localhost:3000通信100%同源自然无跨域。Vite配置vite.config.tsexport default defineConfig({ server: { proxy: { /api: { target: https://api.example.com, changeOrigin: true, // 修改origin头为target避免后端校验失败 rewrite: (path) path.replace(/^\/api/, ), // 去掉/api前缀 secure: false, // 若target是https且证书无效设为false } } } })Webpack配置vue.config.jsmodule.exports { devServer: { proxy: { /api: { target: https://api.example.com, changeOrigin: true, pathRewrite: { ^/api: } } } } }关键参数详解changeOrigin: true这是灵魂选项。它会把请求头中的Origin从http://localhost:3000改成https://api.example.com否则某些后端如Spring Security会因Origin不匹配而拒绝请求rewrite必须和前端代码里的请求路径对齐。比如前端写fetch(/api/user)后端接口是https://api.example.com/user那么rewrite就要去掉/api否则会请求https://api.example.com/api/user404secure: false开发时若后端用自签名HTTPS证书浏览器会报SSL错误设为false可跳过证书校验仅限开发环境。注意事项代理只在npm run dev时生效打包后的生产环境完全无效。很多同学把开发代理当成永久解决方案结果上线后全跪。正确的做法是开发用代理上线用Nginx反向代理见3.4节两者配置保持一致实现无缝切换。3.4 Nginx反向代理生产环境“终极保险”一次配置全站生效当项目上线前端静态资源部署在CDN或对象存储后端API部署在独立服务器时最稳妥的跨域解法是用Nginx做反向代理让前后端在同一个域名下“共存”。用户访问https://www.myapp.comNginx将/api/开头的请求转发给后端其他请求返回前端静态文件。这样前端代码里所有请求都是/api/user天然同源。Nginx完整配置/etc/nginx/conf.d/myapp.confupstream backend { server 192.168.1.100:8080; # 后端服务器IP和端口 keepalive 32; # 保持长连接提升性能 } server { listen 443 ssl http2; server_name www.myapp.com; ssl_certificate /path/to/fullchain.pem; ssl_certificate_key /path/to/privkey.pem; # 前端静态资源 location / { root /var/www/myapp/dist; try_files $uri $uri/ /index.html; # 支持Vue Router history模式 } # API反向代理 location /api/ { proxy_pass https://backend/; # 注意末尾的/表示去除/api前缀 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 关键添加CORS头让后端无需关心跨域 add_header Access-Control-Allow-Origin https://www.myapp.com; add_header Access-Control-Allow-Methods GET, POST, OPTIONS, PUT, DELETE; add_header Access-Control-Allow-Headers DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization; add_header Access-Control-Expose-Headers Content-Length,Content-Range; add_header Access-Control-Allow-Credentials true; # 处理预检请求 if ($request_method OPTIONS) { add_header Access-Control-Allow-Origin https://www.myapp.com; add_header Access-Control-Allow-Methods GET, POST, OPTIONS, PUT, DELETE; add_header Access-Control-Allow-Headers DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization; add_header Access-Control-Allow-Credentials true; add_header Access-Control-Max-Age 1728000; add_header Content-Type text/plain; charsetutf-8; add_header Content-Length 0; return 204; } } }配置要点解析proxy_pass https://backend/末尾的/至关重要。它表示将/api/user重写为/user再转发否则会变成/api/user导致后端路由匹配失败add_header在Nginx层统一添加CORS头后端代码可完全删除CORS逻辑降低耦合if ($request_method OPTIONS)专门拦截预检请求返回204状态码No Content避免请求落到后端造成不必要的压力keepalive启用上游连接池减少TCP握手开销QPS提升显著实测提升30%。4. POST/GET跨域实操全流程从代码编写到线上验证一步不落4.1 前端代码编写规范避免“自找麻烦”的5个细节很多跨域问题根源在前端代码的随意性。遵循以下规范能规避80%的低级错误1. 明确请求类型合理选择Content-Type简单表单提交无文件用application/x-www-form-urlencoded这是浏览器默认行为无需手动设置// ✅ 正确让浏览器自动序列化 fetch(/api/login, { method: POST, body: new URLSearchParams({ username: a, password: b }) }); // ❌ 错误手动设header却没序列化body fetch(/api/login, { method: POST, headers: { Content-Type: application/x-www-form-urlencoded }, body: JSON.stringify({ username: a, password: b }) // 服务器收不到 });2. 携带Cookie的POST必须开启credentials登录态通常依赖Cookie若忘记credentials: include后端无法读取session// ✅ 必须同时设置两项 fetch(/api/profile, { method: POST, credentials: include, // 关键 headers: { Content-Type: application/json }, body: JSON.stringify({ id: 1 }) }); // ⚠️ 后端CORS头也必须配 Access-Control-Allow-Credentials: true // 且 Access-Control-Allow-Origin 不能为 *3. GET请求的缓存陷阱浏览器对GET请求有强缓存机制可能导致跨域请求被缓存而CORS头未更新// ✅ 强制禁用缓存开发调试用 fetch(/api/data?t Date.now(), { cache: no-store }); // ✅ 生产环境用ETag或Last-Modified4. Axios全局配置防遗漏在main.js中统一设置避免每个请求都手写axios.defaults.baseURL /api; // 与Nginx代理路径对齐 axios.defaults.withCredentials true; // 全局开启cookie axios.interceptors.request.use(config { if (config.method post config.headers[Content-Type] application/json) { config.headers[X-Requested-With] XMLHttpRequest; // 便于后端识别 } return config; });5. 错误处理必须区分网络错误与跨域错误try { const res await fetch(/api/user); if (!res.ok) throw new Error(HTTP error! status: ${res.status}); const data await res.json(); } catch (err) { if (err.name TypeError err.message.includes(failed to fetch)) { // 网络错误DNS失败、服务器宕机 console.error(网络不可达请检查网络连接); } else if (err.message.includes(CORS)) { // 跨域错误明确提示后端配置问题 console.error(接口跨域配置异常请联系后端同学); } else { // 其他错误HTTP状态码错误、JSON解析失败等 console.error(请求失败:, err); } }4.2 后端CORS配置实录Spring Boot与Express双案例Spring Boot 2.4推荐使用CorsConfigurationSourceConfiguration public class WebConfig { Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList(https://www.myapp.com, https://myapp.com)); configuration.setAllowedMethods(Arrays.asList(GET, POST, PUT, DELETE, OPTIONS)); configuration.setAllowedHeaders(Arrays.asList(Content-Type, Authorization, X-Requested-With)); configuration.setAllowCredentials(true); // 允许cookie configuration.setMaxAge(3600L); // 预检结果缓存1小时 UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/api/**, configuration); // 仅对/api路径生效 return source; } }避坑点Spring Boot 2.4废弃了CrossOrigin注解的全局配置能力必须用CorsConfigurationSourceBeansetAllowedOrigins必须用具体域名列表*和null都会导致withCredentialstrue时失效registerCorsConfiguration(/api/**, ...)的路径模式要和前端请求路径严格匹配/api/*不匹配/api/user/1。Express 4.x中间件方式// corsMiddleware.js const cors require(cors); const corsOptions { origin: function (origin, callback) { const allowedOrigins [https://www.myapp.com, https://myapp.com]; if (!origin || allowedOrigins.indexOf(origin) ! -1) { callback(null, true); } else { callback(new Error(Not allowed by CORS)); } }, credentials: true, optionsSuccessStatus: 200 // 一些老版本浏览器需要 }; module.exports cors(corsOptions);// app.js const corsMiddleware require(./corsMiddleware); app.use(/api, corsMiddleware); // 仅对/api子路径启用 app.use(/api, (req, res, next) { // 业务逻辑 next(); });实测对比直接app.use(cors())简单粗暴但生产环境不安全允许所有源app.use(/api, cors(...))精准控制且中间件顺序很重要——CORS中间件必须在业务路由之前否则OPTIONS请求无法被捕获。4.3 Nginx代理上线全流程从测试到灰度发布Step 1本地Nginx测试Docker最快# docker-compose.yml version: 3 services: nginx: image: nginx:alpine ports: - 8080:80 volumes: - ./nginx.conf:/etc/nginx/conf.d/default.conf - ./dist:/usr/share/nginx/html depends_on: - backend backend: image: my-backend:latest expose: - 8080启动后访问http://localhost:8080用DevTools Network面板确认所有/api/请求的Remote Address显示为localhost:8080同源响应头中Access-Control-Allow-Origin存在且值为localhost:8080POST请求的Request Method是POST非OPTIONS说明预检已通过。Step 2线上灰度发布为避免全量上线风险采用Nginx的split_clients模块做灰度split_clients $request_id $backend_group { 0.5% backend-v1; * backend-v2; } upstream backend-v1 { server 192.168.1.101:8080; } upstream backend-v2 { server 192.168.1.102:8080; } location /api/ { proxy_pass https://$backend_group; # 其他proxy配置... }将5%流量导到新后端监控错误率、响应时间稳定后再切全量。Step 3线上验证清单✅ 使用curl -I https://www.myapp.com/api/user检查响应头是否含Access-Control-Allow-Origin✅ 在Chrome隐身窗口访问打开DevTools确认Network中无跨域报错✅ 用手机访问测试iOS/Android各主流浏览器✅ 模拟弱网Chrome DevTools Network Throttling Slow 3G确认超时重试逻辑正常。5. 常见问题与排查技巧实录那些年我们踩过的“跨域”坑5.1 预检请求OPTIONS静默失败看不见的拦路虎现象前端代码里明明发的是POST但Network面板里只看到一个OPTIONS请求状态码200而真正的POST请求根本没出现Console里也没有任何报错。原因分析这是最隐蔽的跨域问题。浏览器在发送POST前会先发OPTIONS预检。如果预检响应中缺少Access-Control-Allow-Headers比如前端发了Authorization头但后端没在Allow-Headers里声明浏览器会直接取消后续POST请求且不抛任何JS错误你只能在Network里看到OPTIONS成功但业务请求消失。排查步骤在Network面板点击那个成功的OPTIONS请求切到Headers标签页检查Response Headers里是否有Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers对比Request Headers里的Access-Control-Request-Headers浏览器自动添加确认其值如authorization, content-type是否全部出现在Access-Control-Allow-Headers的响应值中。解决方案后端必须在OPTIONS响应中精确返回前端所需的所有header// Express示例动态读取预检请求头 app.options(/api/*, (req, res) { const requestedHeaders req.headers[access-control-request-headers]; res.header(Access-Control-Allow-Origin, https://www.myapp.com); res.header(Access-Control-Allow-Methods, POST, GET, OPTIONS); res.header(Access-Control-Allow-Headers, requestedHeaders || Content-Type, Authorization); res.sendStatus(200); });或在Nginx中统一配置见3.4节一劳永逸。5.2 “已拦截跨源请求”但后端日志无记录请求根本没到后端现象Console报Blocked by CORS policy但后端服务器日志里完全没有这条请求的痕迹仿佛石沉大海。根本原因请求被更前置的中间件拦截了常见于CDN如Cloudflare配置了WAF规则将带Origin头的请求误判为爬虫而拦截公司内网网关如Kong、Traefik启用了安全策略拒绝Origin头为空或非法的请求Nginx配置了if语句错误地return了403。排查方法绕过CDN在hosts文件中将域名直接指向后端服务器IP再测试查看CDN/WAF控制台的实时日志搜索对应时间戳的Origin头在Nginx的error.log中搜索access forbidden关键字。真实案例某金融项目上线后iOS用户全量报跨域安卓正常。排查发现是Cloudflare的“Browser Integrity Check”功能对iOS Safari的User-Agent做了特殊校验导致OPTIONS请求被拦截。关闭该功能后立即恢复。5.3 本地开发一切正常上线后跨域环境差异陷阱现象npm run serve完美运行npm run build部署到Nginx后所有API请求404或跨域。核心原因开发代理Vite/Webpack和生产Nginx代理的路径重写逻辑不一致。开发时Vite代理将/api/user转发到https://api.com/user自动去掉/api生产Nginx配置错误写成了proxy_pass https://api.com;没加/导致请求变成https://api.com/api/user404。验证与修复在生产环境浏览器中右键“查看页面源代码”确认script引入的JS文件路径是否正确如/static/js/app.js在Network面板点击一个失败的API请求看Request URL是https://www.myapp.com/api/user正确还是https

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑