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

文章来源声明: 原文作者:程序员Flycan; 来源站点:掘金; 原文链接:https://juejin.cn/post/7689440529826316288; 本文基于上述来源整理/加工,觅优补充点评,仅供技术学习交流。版权归原作者所有。
觅优短评

把 Proxy 拆成“浏览器请求什么、代理转发到哪、后端收到什么路径”三问,配置不再靠死记,适合被跨域和路径重写绕晕的前端开发者速查。

在现代前端开发中,由于浏览器受到 **同源策略(Same-Origin Policy)** 的限制,前端请求不同源的后端接口时,经常会遇到跨域问题。

在开发阶段,一个非常常见的解决方案,就是通过 Vite、Webpack Dev Server 等本地开发服务器配置 Proxy(代理) :让浏览器只与本地开发服务器通信,再由开发服务器代替浏览器向真正的后端服务器发送请求。

本文将从 Axios 请求地址、代理原理、Vite / Webpack 配置以及路径重写几个方面,梳理前端 Proxy 的完整工作流程。


一、为什么通常不在 Axios 中写死后端域名?

在实际项目中,一般不建议直接在业务代码中硬编码后端地址,例如:

https:<span>//example.com</span>

一种常见做法,是让 Axios 使用统一的相对路径前缀:

<span>// src/utils/request.js</span>
<span>import</span> axios <span>from</span> <span>'axios'</span>

<span>const</span> service = axios.<span>create</span>({
  <span>baseURL</span>: <span>'/api'</span>,
  <span>timeout</span>: <span>5000</span>
})

<span>export</span> <span>default</span> service

例如:

service.<span>get</span>(<span>'/category'</span>)

最终 Axios 请求的路径就是:

/api/category

💡 这里发生了什么?

假设当前前端开发服务器运行在:

http:<span>//localhost:3000</span>

由于 /api/category 是相对当前 Origin 的路径,因此浏览器实际发送的请求会指向:

http://localhost:3000/api/category

注意,此时浏览器并没有直接请求真正的后端服务器。

从浏览器的角度来看:

页面:http://localhost:3000
请求:http://localhost:3000/api/category

二者协议、主机和端口均一致,因此属于同源请求,不会因为这个请求本身触发浏览器的跨域限制。

随后,本地开发服务器会根据 Proxy 配置,将 /api 开头的请求转发到真正的后端服务器。

这样做还有一个重要好处:前端业务代码不需要绑定具体的后端域名。

开发环境中,可以通过 Vite / Webpack Dev Server 转发 /api;

生产环境中,也可以由 Nginx、网关或其他反向代理服务转发 /api。

于是前端始终只需要请求:

/api/xxx

至于这个请求最终被转发到哪里,可以交给不同环境下的服务器配置决定。


二、代理服务器是如何“偷梁换柱”的?

可以把本地开发服务器想象成站在浏览器和后端服务器之间的一个中转站:

浏览器
   │
   │ <span>GET</span> /api/category
   ▼
本地开发服务器
<span>localhost:</span><span>3000</span>
   │
   │ Proxy 转发
   ▼
真实后端服务器
example.com

整个过程可以分成三步。

1. 拦截请求

浏览器发送:

http://localhost:3000/api/category

Vite 或 Webpack Dev Server 发现这个请求以 /api 开头,与 Proxy 规则匹配,于是将其交给代理处理。

2. 修改目标地址

假设 Proxy 配置:

target: <span>'https</span>:<span>//example.com'</span>

开发服务器就会把请求转发到 https://example.com 对应的接口路径。

至于 /api 是否保留,则取决于有没有配置 rewrite 或 pathRewrite。

3. 由开发服务器请求真正的后端

真正向后端服务器发送请求的是本地开发服务器,而不是浏览器页面中的 JavaScript。

浏览器同源策略主要约束的是浏览器环境中的跨源访问,并不会像约束前端 JavaScript 一样限制服务器之间正常的 HTTP 通信。

因此:

浏览器
   ↓
本地开发服务器
   ↓
真实后端服务器

这条链路绕开了浏览器直接跨域请求后端所带来的限制。

后端返回数据后,本地开发服务器再把响应转交给浏览器。

对于前端代码来说,它始终认为自己请求的是:

localhost:3000/api/...


三、Vite 与 Webpack 代理配置对比

虽然 Vite 和 Webpack Dev Server 的配置语法有所不同,但核心逻辑完全一致:

匹配某个请求前缀 → 转发到目标服务器 → 根据需要重写路径

1. Vite:vite.config.js

Vite 在 server.proxy 中配置代理,路径重写通常使用 rewrite 函数:

<span>import</span> { defineConfig } <span>from</span> <span>'vite'</span>

<span>export</span> <span>default</span> <span>defineConfig</span>({
  <span>server</span>: {
    <span>proxy</span>: {
      <span>'/api'</span>: {
        <span>target</span>: <span>'https://example.com'</span>,
        <span>changeOrigin</span>: <span>true</span>,

        <span>// 如果真实后端接口不包含 /api,</span>
        <span>// 则转发前将 /api 去掉</span>
        <span>rewrite</span>: <span>(<span>path</span>) =></span> path.<span>replace</span>(<span>/^/</span>api/, <span>''</span>)
      }
    }
  }
})

例如浏览器请求:

http://localhost:3000/api/category

经过:

<span>rewrite</span>: <span>(<span>path</span>) =></span> path.<span>replace</span>(<span>/^/</span>api/, <span>''</span>)

之后,转发给后端的路径变成:

/category

因此最终请求:

https:<span>//example.com/category</span>


2. Webpack:webpack.config.js

在常见的 Webpack Dev Server 配置中,可以通过 devServer.proxy 配置代理。

不同 Webpack Dev Server 版本的具体 API 可能有所差异;在使用支持 pathRewrite 的代理配置时,可以写成:

module<span>.exports</span> = {
  devServer: {
    proxy: {
      '/api': {
        target: <span>'https://example.com'</span>,
        changeOrigin: true,

        pathRewrite: {
          '^/api': <span>''</span>
        }
      }
    }
  }
}

其效果与前面的 Vite 配置类似:

/api/category
      ↓
/category
      ↓
https://example.com/category

⚠️ 注意: Webpack Dev Server 不同版本的 Proxy 配置格式存在差异。如果使用较新的版本,应以当前版本官方文档为准,不要直接照搬旧项目中的配置。


四、changeOrigin: true 到底有什么作用?

这是 Proxy 配置中一个很容易被误解的选项。

很多教程会把它简单解释成:

“允许跨域。”

这种说法并不准确。

changeOrigin 的主要作用,是在代理向目标服务器发送请求时,修改请求中的 Host 等与目标 Origin 相关的请求信息,使其更符合目标服务器的地址。

例如:

<span>target:</span> <span>'https://example.com',</span>
<span>changeOrigin:</span> <span>true</span>

代理转发请求时,会让请求的 Host 等相关信息更符合目标服务器:

example.com

而不是继续使用本地开发服务器:

<span>localhost:3000</span>

相关的信息。

这对于某些依赖 Host 判断请求来源、虚拟主机配置或反向代理规则的后端服务器尤其重要。

因此:

Proxy 能解决开发环境跨域问题,并不是因为 changeOrigin: true“关闭了跨域限制”,而是因为浏览器只请求同源的本地开发服务器,真正的跨服务器请求由代理服务器完成。


五、⚠️ 核心避坑:/api 到底会不会自动消失?

这是配置 Proxy 时最容易混淆的问题之一。

先记住一个原则:

Proxy 不会凭空帮你决定 /api 应不应该存在。

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

  1. 浏览器发送了什么路径;
  2. Proxy 是否配置了路径重写。

假设前端统一请求:

/api/category

情况 A:真实后端接口不包含 /api

后端真实接口:

https:<span>//example.com/category</span>

但前端请求:

/api/category

那么就需要配置:

<span>rewrite</span>: <span>(<span>path</span>) =></span> path.<span>replace</span>(<span>/^/</span>api/, <span>''</span>)

于是:

/api/category
      ↓ rewrite
/category
      ↓ target
https://example.com/category

此时 /api 的作用主要是作为前端代理规则的匹配前缀。


情况 B:真实后端接口本身包含 /api

假设真实接口就是:

https:<span>//example.com/api/category</span>

那么通常不应该删除 /api。

配置可以直接写:

<span>'/api'</span>: {
  target: <span>'https://example.com'</span>,
  changeOrigin: <span>true</span>
}

于是:

/api/category
      ↓
https://example.com/api/category

不需要额外配置 rewrite。


六、一张图理解完整请求链路

假设 Axios 配置:

<span>baseURL:</span> <span>'/api'</span>

Proxy 配置:

<span>target</span>: <span>'https://example.com'</span>,
<span>rewrite</span>: <span>(<span>path</span>) =></span> path.<span>replace</span>(<span>/^/</span>api/, <span>''</span>)

业务代码:

service.<span>get</span>(<span>'/category'</span>)

完整流程就是:

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 属性名区分大小写。

如果写成:

axios<span>.create</span>({
  baseUrl: <span>'/api'</span>
})

Axios 不会把它识别为正确的基础 URL 配置。

例如业务代码请求:

service.<span>get</span>(<span>'/category'</span>)

最终可能直接请求:

http://localhost:3000/category

而不是预期的:

http://localhost:3000/api/category

如果本地开发服务器不存在对应资源,就很容易出现:

404 Not Found


2. 修改代理配置后没有重启开发服务器

vite.config.js、webpack.config.js、vue.config.js 等属于开发服务器配置。

修改 Proxy 后,如果发现配置似乎没有生效,可以优先尝试重新启动开发服务器:

npm run dev

或者:

npm run serve


3. 先看 Network,再猜 Proxy

遇到接口异常时,不要第一时间反复修改代理配置。

先打开浏览器:

DevTools → Network

确认浏览器实际发送的 Request URL。

例如你原本希望看到:

http://localhost:3000/api/category

结果实际却是:

http://localhost:3000/category

那么问题很可能出在 Axios 的 baseURL。

如果浏览器已经正确请求:

/api/category

但后端依然返回 404,则应该继续检查:

target
rewrite / pathRewrite
真实后端接口路径

这样排查效率会高很多。


八、总结:Proxy 本质上解决了什么?

整个 Proxy 机制可以浓缩成一句话:

让浏览器只访问同源的本地开发服务器,再由开发服务器代替浏览器访问真正的后端。

因此,开发环境下常见的架构实际上是:

前端业务代码
     ↓
Axios:/api/xxx
     ↓
浏览器
     ↓
Vite / Webpack Dev <span>Server</span>
     ↓
Proxy
     ↓
真实后端 API

其中:

  • baseURL:统一前端请求前缀;
  • target:指定真正的后端服务器;
  • rewrite / pathRewrite:决定是否修改请求路径;
  • changeOrigin:调整代理请求中的 Host 等相关信息;
  • Proxy:负责真正的请求转发。

理解这些概念之后,就不需要再死记某一份 Proxy 配置。

以后看到:

<span>'/api'</span><span>:</span> {
  <span>target:</span> <span>'...'</span>,
  <span>changeOrigin:</span> <span>true</span>,
  <span>rewrite:</span> <span>...</span>
}

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

浏览器现在请求什么?

代理准备转发到哪里?

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

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