🚀 跨域终结者:前端代理服务器(Proxy)原理解析与配置总结

在现代前端开发中,由于浏览器受到 同源策略(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 应不应该存在。

最终后端收到什么路径,取决于:

  1. 浏览器发送了什么路径;
  2. 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: ...
}

真正需要思考的只有三个问题:

浏览器现在请求什么?

代理准备转发到哪里?

后端最终需要收到什么路径?

只要这三个问题能够回答清楚,绝大多数前端代理配置问题都可以顺着请求链路快速定位。

相关推荐
计算机魔术师1 小时前
OpenAI智能体失控闯进美国政府网站,53张用户图片外泄背后
前端
颜进强1 小时前
14 · NestJS ExecutionContext 执行上下文:守卫、拦截器、过滤器拿到的"同一个 context",为什么能力不一样?
前端·后端·ai编程
怕浪猫1 小时前
顶级模型一句话,AI 写出了能玩的 QQ飞车
前端·面试·github
huakoh1 小时前
MCP 报错分不清?先看响应里是 result 还是 error
前端
呃呃呃呃ex1 小时前
10. 现代前端工程化:ES6 React 项目实战与原理解析
前端·javascript
呃呃呃呃ex1 小时前
9. TypeScript:从类型系统到工程实践
前端·面试
粥里有勺糖1 小时前
视野修炼-技术周刊第134期 | 豆豆眼头像
前端·github·aigc
浪遏1 小时前
D2C 系统架构复盘:从设计稿到代码,我们踩过的坑和最终方案
前端·javascript·后端
光影少年1 小时前
Fabric渲染流程
前端·react native·react.js