在现代前端开发中,由于浏览器受到 同源策略(Same-Origin Policy) 的限制,前端请求不同源的后端接口时,经常会遇到跨域问题。
在开发阶段,一个非常常见的解决方案,就是通过 Vite、Webpack Dev Server 等本地开发服务器配置 Proxy(代理) :让浏览器只与本地开发服务器通信,再由开发服务器代替浏览器向真正的后端服务器发送请求。
本文将从 Axios 请求地址、代理原理、Vite / Webpack 配置以及路径重写几个方面,梳理前端 Proxy 的完整工作流程。
一、为什么通常不在 Axios 中写死后端域名?
在实际项目中,一般不建议直接在业务代码中硬编码后端地址,例如:
arduino
https://example.com
一种常见做法,是让 Axios 使用统一的相对路径前缀:
javascript
// src/utils/request.js
import axios from 'axios'
const service = axios.create({
baseURL: '/api',
timeout: 5000
})
export default service
例如:
csharp
service.get('/category')
最终 Axios 请求的路径就是:
bash
/api/category
💡 这里发生了什么?
假设当前前端开发服务器运行在:
arduino
http://localhost:3000
由于 /api/category 是相对当前 Origin 的路径,因此浏览器实际发送的请求会指向:
bash
http://localhost:3000/api/category
注意,此时浏览器并没有直接请求真正的后端服务器。
从浏览器的角度来看:
bash
页面:http://localhost:3000
请求:http://localhost:3000/api/category
二者协议、主机和端口均一致,因此属于同源请求,不会因为这个请求本身触发浏览器的跨域限制。
随后,本地开发服务器会根据 Proxy 配置,将 /api 开头的请求转发到真正的后端服务器。
这样做还有一个重要好处:前端业务代码不需要绑定具体的后端域名。
开发环境中,可以通过 Vite / Webpack Dev Server 转发 /api;
生产环境中,也可以由 Nginx、网关或其他反向代理服务转发 /api。
于是前端始终只需要请求:
bash
/api/xxx
至于这个请求最终被转发到哪里,可以交给不同环境下的服务器配置决定。
二、代理服务器是如何"偷梁换柱"的?
可以把本地开发服务器想象成站在浏览器和后端服务器之间的一个中转站:
vbnet
浏览器
│
│ GET /api/category
▼
本地开发服务器
localhost:3000
│
│ Proxy 转发
▼
真实后端服务器
example.com
整个过程可以分成三步。
1. 拦截请求
浏览器发送:
bash
http://localhost:3000/api/category
Vite 或 Webpack Dev Server 发现这个请求以 /api 开头,与 Proxy 规则匹配,于是将其交给代理处理。
2. 修改目标地址
假设 Proxy 配置:
rust
target: 'https://example.com'
开发服务器就会把请求转发到 https://example.com 对应的接口路径。
至于 /api 是否保留,则取决于有没有配置 rewrite 或 pathRewrite。
3. 由开发服务器请求真正的后端
真正向后端服务器发送请求的是本地开发服务器,而不是浏览器页面中的 JavaScript。
浏览器同源策略主要约束的是浏览器环境中的跨源访问,并不会像约束前端 JavaScript 一样限制服务器之间正常的 HTTP 通信。
因此:
浏览器
↓
本地开发服务器
↓
真实后端服务器
这条链路绕开了浏览器直接跨域请求后端所带来的限制。
后端返回数据后,本地开发服务器再把响应转交给浏览器。
对于前端代码来说,它始终认为自己请求的是:
bash
localhost:3000/api/...
三、Vite 与 Webpack 代理配置对比
虽然 Vite 和 Webpack Dev Server 的配置语法有所不同,但核心逻辑完全一致:
匹配某个请求前缀 → 转发到目标服务器 → 根据需要重写路径
1. Vite:vite.config.js
Vite 在 server.proxy 中配置代理,路径重写通常使用 rewrite 函数:
javascript
import { defineConfig } from 'vite'
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'https://example.com',
changeOrigin: true,
// 如果真实后端接口不包含 /api,
// 则转发前将 /api 去掉
rewrite: (path) => path.replace(/^/api/, '')
}
}
}
})
例如浏览器请求:
bash
http://localhost:3000/api/category
经过:
javascript
rewrite: (path) => path.replace(/^/api/, '')
之后,转发给后端的路径变成:
bash
/category
因此最终请求:
arduino
https://example.com/category
2. Webpack:webpack.config.js
在常见的 Webpack Dev Server 配置中,可以通过 devServer.proxy 配置代理。
不同 Webpack Dev Server 版本的具体 API 可能有所差异;在使用支持 pathRewrite 的代理配置时,可以写成:
css
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'https://example.com',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
}
}
其效果与前面的 Vite 配置类似:
bash
/api/category
↓
/category
↓
https://example.com/category
⚠️ 注意: Webpack Dev Server 不同版本的 Proxy 配置格式存在差异。如果使用较新的版本,应以当前版本官方文档为准,不要直接照搬旧项目中的配置。
四、changeOrigin: true 到底有什么作用?
这是 Proxy 配置中一个很容易被误解的选项。
很多教程会把它简单解释成:
"允许跨域。"
这种说法并不准确。
changeOrigin 的主要作用,是在代理向目标服务器发送请求时,修改请求中的 Host 等与目标 Origin 相关的请求信息,使其更符合目标服务器的地址。
例如:
vbnet
target: 'https://example.com',
changeOrigin: true
代理转发请求时,会让请求的 Host 等相关信息更符合目标服务器:
example.com
而不是继续使用本地开发服务器:
makefile
localhost:3000
相关的信息。
这对于某些依赖 Host 判断请求来源、虚拟主机配置或反向代理规则的后端服务器尤其重要。
因此:
Proxy 能解决开发环境跨域问题,并不是因为
changeOrigin: true"关闭了跨域限制",而是因为浏览器只请求同源的本地开发服务器,真正的跨服务器请求由代理服务器完成。
五、⚠️ 核心避坑:/api 到底会不会自动消失?
这是配置 Proxy 时最容易混淆的问题之一。
先记住一个原则:
Proxy 不会凭空帮你决定
/api应不应该存在。
最终后端收到什么路径,取决于:
- 浏览器发送了什么路径;
- Proxy 是否配置了路径重写。
假设前端统一请求:
bash
/api/category
情况 A:真实后端接口不包含 /api
后端真实接口:
arduino
https://example.com/category
但前端请求:
bash
/api/category
那么就需要配置:
javascript
rewrite: (path) => path.replace(/^/api/, '')
于是:
bash
/api/category
↓ rewrite
/category
↓ target
https://example.com/category
此时 /api 的作用主要是作为前端代理规则的匹配前缀。
情况 B:真实后端接口本身包含 /api
假设真实接口就是:
arduino
https://example.com/api/category
那么通常不应该删除 /api。
配置可以直接写:
arduino
'/api': {
target: 'https://example.com',
changeOrigin: true
}
于是:
bash
/api/category
↓
https://example.com/api/category
不需要额外配置 rewrite。
六、一张图理解完整请求链路
假设 Axios 配置:
vbnet
baseURL: '/api'
Proxy 配置:
javascript
target: 'https://example.com',
rewrite: (path) => path.replace(/^/api/, '')
业务代码:
csharp
service.get('/category')
完整流程就是:
bash
Axios
│
│ /api/category
▼
浏览器
│
│ http://localhost:3000/api/category
▼
Vite / Webpack Dev Server
│
│ 匹配 /api
│
│ rewrite:/api/category → /category
▼
https://example.com/category
│
│ 返回 JSON
▼
本地开发服务器
│
▼
浏览器
│
▼
Axios 获取响应
理解这条链路之后,Proxy 的配置其实就非常简单了:
前端只负责请求统一的相对路径,开发服务器负责把请求转发到真正的后端。
七、开发中常见的关联问题
1. Axios 的 baseURL 大小写写错
Axios 中正确的配置项是:
baseURL
而不是:
baseUrl
JavaScript 属性名区分大小写。
如果写成:
css
axios.create({
baseUrl: '/api'
})
Axios 不会把它识别为正确的基础 URL 配置。
例如业务代码请求:
csharp
service.get('/category')
最终可能直接请求:
bash
http://localhost:3000/category
而不是预期的:
bash
http://localhost:3000/api/category
如果本地开发服务器不存在对应资源,就很容易出现:
404 Not Found
2. 修改代理配置后没有重启开发服务器
vite.config.js、webpack.config.js、vue.config.js 等属于开发服务器配置。
修改 Proxy 后,如果发现配置似乎没有生效,可以优先尝试重新启动开发服务器:
arduino
npm run dev
或者:
arduino
npm run serve
3. 先看 Network,再猜 Proxy
遇到接口异常时,不要第一时间反复修改代理配置。
先打开浏览器:
DevTools → Network
确认浏览器实际发送的 Request URL。
例如你原本希望看到:
bash
http://localhost:3000/api/category
结果实际却是:
bash
http://localhost:3000/category
那么问题很可能出在 Axios 的 baseURL。
如果浏览器已经正确请求:
bash
/api/category
但后端依然返回 404,则应该继续检查:
target
rewrite / pathRewrite
真实后端接口路径
这样排查效率会高很多。
八、总结:Proxy 本质上解决了什么?
整个 Proxy 机制可以浓缩成一句话:
让浏览器只访问同源的本地开发服务器,再由开发服务器代替浏览器访问真正的后端。
因此,开发环境下常见的架构实际上是:
arduino
前端业务代码
↓
Axios:/api/xxx
↓
浏览器
↓
Vite / Webpack Dev Server
↓
Proxy
↓
真实后端 API
其中:
baseURL:统一前端请求前缀;target:指定真正的后端服务器;rewrite / pathRewrite:决定是否修改请求路径;changeOrigin:调整代理请求中的 Host 等相关信息;Proxy:负责真正的请求转发。
理解这些概念之后,就不需要再死记某一份 Proxy 配置。
以后看到:
yaml
'/api': {
target: '...',
changeOrigin: true,
rewrite: ...
}
真正需要思考的只有三个问题:
浏览器现在请求什么?
代理准备转发到哪里?
后端最终需要收到什么路径?
只要这三个问题能够回答清楚,绝大多数前端代理配置问题都可以顺着请求链路快速定位。