
先回忆一个场景vue 项目 npm run dev 跑起来了页面正常渲染一调后端接口控制台立刻飘红——Access to XMLHttpRequest at http://localhost:8080/... has been blocked by CORS policy。后端同事把接口地址发给你你拿 Postman 调得好好的可浏览器就是不让过。这种本地开发跨域问题在 vite 项目里最标准的解法就是 proxy打开 vite.config.js配置 server.proxy让 vite 自带开发服务器帮你转发请求。前端不用后端开 CORS、不用装浏览器插件、不用改任何业务代码纯配置就能把跨域按下去。这篇文章就以 vue vite 为背景把跨域原理、proxy 配置的细节、实际踩过的坑一次聊透希望正在被跨域折磨的同学看完能少走弯路。1. 先搞清楚为什么会跨域同源策略和代理的本质1.1 浏览器同源策略到底拦的是什么同源策略是浏览器的一个安全机制。所谓同源指的是协议、域名、端口三者完全一致。http://localhost:5173 和 http://localhost:8080端口不一样算不同源http://127.0.0.1:5173 和 http://localhost:5173虽然看起来差不多但域名写法不一样也算不同源。这个标准非常严格稍微差一个字符都不行。本地开发普遍就是这个局面vue 项目跑在 5173 端口后端接口跑在 8080 端口二者不是同一个 origin。浏览器发请求的时候发现目标地址跨域于是把服务器返回的响应拦下来不给前端 JS 读取。很多人容易有个误区觉得浏览器是把请求拦住了。实际上请求已经发出去了后端也正常处理了数据也返回了但浏览器在“接收”这一步拦截了前端拿不到 response所以表现成报错。这个设计本来是为了保护用户防止恶意网站读取你在其他网站的数据。但对联调阶段的开发者来说它就是一个纯纯的阻碍。有个生活化的类比同源策略就像小区门禁快递员想进你家送快递门禁系统发现这人不是本小区的东西到门口就被拦下了。快递其实已经送到小区门口了只是进不了门。1.2 vite proxy 为什么能“绕过”跨域跨域是浏览器的规矩不是 HTTP 协议本身的规矩。vite dev server 跑在 Node 环境里Node 发 HTTP 请求根本不看同源策略。于是 vite 就把这个特性利用起来本地开发时浏览器请求的地址写成 vite dev server 自己的地址再由 dev server 转发到真正的后端接口。整个链路是这样的浏览器发出请求 http://localhost:5173/api/user/list → vite dev server 收到请求 → dev server 把请求转发到 http://localhost:8080/api/user/list → 后端返回数据 → dev server 再把数据返回给浏览器。浏览器全程只和 localhost:5173 通信这是同源请求浏览器不拦真正的跨域请求由 dev server 在服务端完成服务端没有跨域概念天然能通。这个思路和 Nginx 反向代理是一模一样的vite proxy 本质就是一个内置在开发服务器里的轻量反向代理。理解这一点很重要它能帮你定位很多奇怪的问题。比如有同学问“为什么代理配好了还是跨域”多半是核心逻辑出了偏差前端代码里请求地址写死了后端的完整地址比如 axios 的 baseURL 直接写 http://localhost:8080这样浏览器就会绕过 vite dev server 直接请求后端proxy 配得再完整也接管不到这个请求。2. 手把手配置 vite.config.jsproxy 三个核心参数2.1 最小可用配置长什么样在 vite 项目的根目录找到 vite.config.js也可能是 vite.config.ts配置 server 对象下的 proxy。一个最小可用的配置是这样的// vite.config.js import { defineConfig } from vite import vue from vitejs/plugin-vue export default defineConfig({ plugins: [vue()], server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true, } } } })配置完之后你在前端请求 /api/user/listvite 就会把它转发到 http://localhost:8080/api/user/list。浏览器看到的请求地址始终是 http://localhost:5173/api/user/list同源不会跨域。前端请求代码基本不用动唯一要注意的是尽量用相对路径// 推荐相对路径走 vite 代理 axios.get(/api/user/list) // 不推荐写死绝对地址代理直接失效 axios.get(http://localhost:8080/api/user/list)这里有一个很多人没注意的细节键名 /api 是匹配规则表示所有以 /api 开头的请求路径都走这个代理。target 是真正的后端地址端口、协议都不能写错http/https 写反了也会出问题。2.2 target、changeOrigin、rewrite 分别有什么用target 不用多讲就是目标服务器需要提醒的是端口别写错、协议别写错。我见过有人排查半天最后发现是 target 把 8080 写成了 80或者 https 写成了 http。changeOrigin 这个参数是很多后端联调半天不通的根源。它控制的是请求转发时要不要把请求头里的 Host 字段改成 target 的域名。HTTP 请求里有一个 Host 头表示你请求的目标主机。默认情况下 vite 转发的请求会保留浏览器发来的 Host也就是 localhost:5173。部分后端接口会校验 Host 或 Referer 头发现来源不是它认识的域名直接拒绝。把 changeOrigin 设为 true 之后转发的请求头里 Host 就被替换成 target 的域名和端口了后端看起来就像是你直接请求它一样。用类比来说changeOrigin 就像是给快递换了一身印着“小区内部人员”的工作服门禁一看是自己人放行。rewrite 是路径重写函数它决定转发时 URL 怎么变化。默认不写就是原样转发——前端请求 /api/user/list转发给 target 的还是 /api/user/list。这个默认行为让很多人困惑所以下一节单独讲。2.3 接口带不带前缀配置差在哪这是我在带新人时讲得最多的一点。rewrite 到底要不要写取决于后端接口到底有没有 /api 这个前缀。先说结论后端接口路径和前端请求路径能对上就不用 rewrite对不上才需要 rewrite 调整。场景 A后端本身就是 /api 开头。后端接口是 http://localhost:8080/api/user/list前端代码里直接请求 /api/user/list。这时候 target 指向 http://localhost:8080转发时 /api/user/list 原样带过去正好和后端匹配不用写 rewrite。场景 B后端没有 /api 前缀。后端实际接口是 http://localhost:8080/user/list但前端为了统一走代理请求地址写了 /api/user/list。此时如果还按场景 A 配置vite 转发过去的是 http://localhost:8080/api/user/list后端根本没有这个路径返回 404。必须加一条 rewrite把开头的 /api 去掉proxy: { /api: { target: http://localhost:8080, changeOrigin: true, rewrite: path path.replace(/^\/api/, ), } }很多同学看见 404 第一反应是“后端接口挂了”实际上后端接口好端端的是你多加了一个 /api。判断方法也很简单把 target 和后端路径拼起来看最终的 URL 是否等于后端真实接口地址。拼出来不对就调整 rewrite。这里有个建议前端请求路径和后端路径的对应关系最好在配置注释里写清楚避免后面接手的同事一脸懵。提示rewrite 里的正则 /^/api/ 只匹配路径开头的 /api不会误伤路径中间出现的 api 字符串。有人图省事写成 path.replace(/api, )不带正则如果路径里恰好有多个 api 相关的片段结果会非常诡异强烈建议用正则写法。3. 进阶配置HTTPS、多环境和多代理规则3.1 后端是 https 或自签名证书时怎么办本地开发时后端有时候是测试服务器域名是 https 的比如 https://test-api.example.com。如果证书正常直接配 target 就行浏览器和 vite 都会正常处理。但如果后端是 IP https或者用了自签名证书vite 转发时通常会报证书校验错误表现为请求一直失败或者直接看到 SSL 相关报错。此时在代理配置里加 secure: false让转发过程跳过 TLS 证书校验。注意这个参数的含义是“不对目标服务器做证书校验”不会影响浏览器本身的证书校验所以不用担心安全问题——反正是开发阶段连接测试服务器用。配置写法proxy: { /api: { target: https://10.0.0.18:8443, changeOrigin: true, secure: false, } }这里有个容易忽略的点如果 target 是 https 协议但忘了写 secure: false报的错可能不是“证书无效”而是代理直接连不上、连接被重置。因为有的自签名证书在握手阶段就被拒绝了表现非常像网络不通。遇到 https 目标地址请求异常先想到 secure 参数。3.2 用环境变量切换 target一套配置跑不同环境项目开发到中后期往往会区分本地、测试、预发环境后端地址各不相同。如果每换一个环境都手动改 vite.config.js容易改错还要重启 dev server。我习惯把 target 做成环境变量在 .env.development 里维护。# .env.development VITE_API_TARGEThttp://localhost:8080然后在 vite.config.js 里用 loadEnv 读取import { defineConfig, loadEnv } from vite import vue from vitejs/plugin-vue export default defineConfig(({ mode }) { const env loadEnv(mode, process.cwd(), ) return { plugins: [vue()], server: { proxy: { /api: { target: env.VITE_API_TARGET, changeOrigin: true, } } } } })这样想切环境只需要改 .env 文件里的 VITE_API_TARGET不用再碰代理配置。团队协作时每个人本地的后端地址可能也不同可以各自维护一份 .env.local不影响别人。有个小经验变量命名规范很重要。Vite 会默认加载 VITE_ 开头的变量到前端代码代理配置里读取的时候同样可以用这个前缀统一管理起来更清晰。3.3 正则匹配、多代理实例和 WebSocket除了字符串前缀匹配proxy 的键还支持正则。比如只想代理 /api 和 /auth 两个前缀可以直接写正则proxy: { ^/(api|auth): { target: http://localhost:8080, changeOrigin: true, } }如果前端要代理多个不同的后端服务配置多个键即可。比如 /api 代理到 Java 服务/upload 代理到文件服务互不干扰。匹配规则是按顺序尝试命中哪个就交给哪个 target所以具体键的顺序一般不影响结果但建议把更具体的路径放前面避免歧义。WebSocket 场景也需要单独提一下。如果项目里有在线聊天、实时推送这类 ws 连接且后端是 ws:// 协议代理配置里要加 ws: true否则 WebSocket 握手可能失败。示例proxy: { /socket: { target: ws://localhost:9000, ws: true, changeOrigin: true, } }最后还有一个 bypass 函数它是 proxy 配置里的高级选项可以针对单个请求动态跳过代理。比如某个路径不想走代理、直接返回一段响应可以这么写proxy: { /api: { target: http://localhost:8080, changeOrigin: true, bypass(req) { if (req.url.includes(/api/mock)) { return /mock-data.json } } } }这个功能不常用但遇到特殊需求时非常省事。不过它属于 http-proxy 的进阶能力新手阶段用不到可以跳过等真有场景了再回来查。4. 常见问题排查改了配置没效果、404、502 怎么定位4.1 判断代理有没有生效先看网络面板遇到代理相关的问题我第一个动作永远是打开浏览器开发者工具的 Network 面板重新触发一次请求看请求的 URL 和状态。这个方法能解决绝大多数排查困惑。如果网络面板里显示的 URL 是 http://localhost:5173/api/user/list说明请求确实被 vite 接管了代理配置是生效的问题很可能出在 target 或 rewrite。如果显示的是 http://localhost:8080/user/list说明请求压根没走代理多半是前端代码里写了绝对地址或者用了某种方式绕过了相对路径。判断代理是否生效还有一个更“暴力”的办法把 target 故意配成一个不存在的端口比如 9999再刷新页面。如果报 ECONNREFUSED 或者类似的连接失败错误说明代理确实在帮你转发只是目标不对。如果请求行为完全没有变化说明请求根本没进代理问题在前端请求地址上。4.2 报错速查404、502、503、ECONNREFUSED 分别是什么意思整理一个速查表方便对照排查现象可能原因排查方向请求走了代理返回 404rewrite 把路径写错了或多加了前缀把 target 和转发路径拼起来对比后端真实接口地址返回 502 Bad Gateway代理和目标服务器之间连接失败确认 target 地址端口是否正确服务是否启动返回 503目标服务不可用或拒绝了请求直接请求 target 地址看能否访问ECONNREFUSED目标端口没有服务在监听检查后端服务是否启动、端口是否正确SSL / 证书相关报错目标是 https 且证书不被信任加 secure: false后端说 Host 不对changeOrigin 没设 true显式配置 changeOrigin: true接口始终报跨域前端写了绝对地址代理没接管改成相对路径带 /api 前缀想特别强调一个排查思路代理转发这件事可以分为“浏览器到 vite”和“vite 到后端”两段。第一段出问题表现是网络面板里根本没有走到代理的请求第二段出问题表现是 vite 正常收到了请求但 target 那边的响应报错。先分清是前一段还是后一段再针对性查效率会高很多。4.3 rewrite 写错、changeOrigin 漏配等高频坑rewrite 写错是我见过最多的坑。除了前面说的不用正则的问题还有人会把 rewrite 写成 path path.replace(/^/api//, )多了一个结尾斜杠结果会导致路径里的分隔符被吃掉了。比如 /api/user/list 变成了 userlist后端自然 404。正确的做法是明确自己要去掉的是什么如果前端请求是 /api/user/list想去掉 /api就用 /^/api/如果想去掉 /api/就用 /^/api//。还有一个高频坑前端请求路径里少了斜杠。比如 /api/user/list 和 /apiuser/list看起来只是笔误但后者根本匹配不到 /api 前缀。匹配规则是前缀匹配不是模糊匹配所以路径要严格控制不能指望代理帮你自动修正。changeOrigin 漏配也经常遇到。如果后端不校验 Host漏配也能跑通所以一直没人注意。但一旦换了个校验严格的后端就会出现“接口偶尔通、换了环境就不通”的诡异问题。我现在的习惯是每套代理规则都无脑加 changeOrigin: true即使暂时用不到也写上后面改动时有据可查。修改 vite.config.js 不生效的问题是另一个高频困扰。vite.config.js 属于配置文件很多情况下 dev server 不会完全热更新配置内容。如果改了配置发现没反应别纠结直接 CtrlC 重启 npm run dev重启后一定生效。这个小动作能避免很多为“玄学 bug”浪费的时间。4.4 代理配好了接口还是报跨域怎么回事这是我在论坛里看到频率很高的问题。代理已经配好target 也正确网络面板显示请求走的是 localhost:5173但控制台还是报跨域错误。这种情况我遇到过几次原因基本就两类。一类是前端代码里用到了自定义请求头比如在 Authorization 之外又加了 X-Token 之类的头。请求带自定义头浏览器在正式请求前会发一个 OPTIONS 预检。走代理后这个预检请求同样由 vite 转发正常来说 vite 会把响应返回但如果后端对 OPTIONS 请求处理不友好或者代理把 OPTIONS 请求拦截了浏览器就会认为预检失败表现依然是跨域。排查时可以专门看 Network 面板里有没有 OPTIONS 请求、状态码是什么。另一类是后端配置了 CORS 相关的响应头但和代理转发后的响应冲突了。比如后端把 Access-Control-Allow-Origin 写死成了某个域名而浏览器请求的 origin 是 localhost:5173哪怕走了代理浏览器同样会拦。这种情况严格来说不是代理的问题而是后端 CORS 配置问题协调后端处理即可。5. 上线之后还会跨域吗开发代理和生产的边界5.1 vite proxy 只在开发环境生效vite proxy 是 dev server 提供的功能npm run build 打包出来的是纯静态文件本身没有 serverproxy 自然不存在。所以上线之后前端部署在 Nginx 或者对象存储上浏览器请求依然会有跨域问题而且比开发环境更麻烦因为这时候没有 vite 帮你挡一挡。如果项目是前后端分离并且部署在不同域名下生产环境的跨域必须从架构层面解决。最推荐的方案是用 Nginx 做反向代理把 /api 开头的请求转发到后端服务让浏览器始终只访问前端域名。这个思路和 vite proxy 完全一致相当于把开发代理搬到了服务器上。一个简单的 Nginx 配置片段location /api/ { proxy_pass http://localhost:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }这里要注意 proxy_pass 结尾的斜杠带斜杠表示会把 /api/ 前缀去掉再转发不带斜杠则保留 /api。这个细节和 vite 里 rewrite 的作用本质相同搞反了同样会 404。5.2 生产环境的跨域方案怎么选还有一条路是后端开 CORS在响应头里加 Access-Control-Allow-Origin。这条路开发省事但要注意如果涉及 Cookie 跨域后端需要配套 Access-Control-Allow-Credentials 等响应头如果 Access-Control-Allow-Origin 用了通配符 *又没法带 Cookie。不少团队一开始为了方便全加星号后面要加会话态时才发现处处是坑整改起来很麻烦。所以我的建议是这样的优先级能走同源就同源用 Nginx 反代把前后端收敛到同一个域名实在不能同源就规范地做 CORS 白名单vite proxy 始终定位为开发工具它的价值是让开发阶段不被跨域绊住而不是替代生产方案。我之前带的一个项目就是这样开发环境统一走 /api 前缀上线之后换成 Nginx 反代前端代码几乎零改动整个迁移非常顺滑。另外有个小实践可以分享前端代码里把请求 baseURL 做成可配置的开发环境指向 /api生产环境指向自己的域名不要写死。这样开发用 vite proxy上线切 Nginx代码几乎不用动联调和部署都省心。配置 vite proxy 这件事门槛其实不高但因为它介于“前端配置”和“服务端代理”之间很多人出了问题容易一头扎进代码里翻。我个人的实操体会是先按“浏览器到 vite、vite 到后端”两段来拆再用网络面板确认请求实际走到了哪绝大多数问题都能快速定位。最后再分享一个小习惯凡是新增代理规则我都在配置里写一行注释标明这个规则对应的后端服务和负责人。等到接口迁移、后端换地址的时候你会感谢这行注释帮你省下的沟通时间。