Vue3项目(由Vite构建)中通过代理解决跨域问题(什么是跨域、代理规则详解、代理举例、实现代理的原理、代理规则的工作机制、为什么直接运行vite指令报错,而运行npm run dev指令却正常)
文章目录
1. 什么是跨域
1.1 本域和同源策略
在了解什么是跨域之前,我们需要先了解两个概念:本域和同源策略
- 本域:同协议、同域名、同端口
- 同源策略:为了保护用户隐私和防止恶意网站窃取数据,浏览器默认只允许与本域的接口进行交互

同源策略规定,一个域下的 JavaScript 脚本不能直接访问或读取另一个域的资源,也不能直接向另一个域发起请求
1.2 跨域的概念
当浏览器发出一个请求时,只要请求URL的协议、域名、端口三者之间任意一个与当前页面URL不同,就称为跨域
| 当前页面URL | 请求URL | 是否跨域 | 原因 |
|---|---|---|---|
| http://www.test.com | http://www.test.com/index.html | 否 | 同源(协议、域名、端口都相同) |
http://www.test.com | https://www.test.com/index.html | 是 | 协议不同 |
http://www.test.com | http://www.baidu.com | 是 | 域名不同 |
http://www.test.com:8080 | http://www.test.com:8088 | 是 | 端口不同 |
2.在Vue3项目(由Vite构建)中通过代理解决跨域问题
2.1 编写vite.config.js配置文件
在项目的根目录下找到vite.config.js配置文件,编写与代理相关的配置

import {fileURLToPath, URL} from 'node:url'
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue()
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
server: {
proxy: {
// 在此处编写代理规则
}
}
})
2.2 代理规则详解
配置代理规则主要关注proxy中的四个属性:
- 要触发代理的路径
- target
- changeOrigin
- rewrite
2.2.1 要触发代理的路径
以下述代理规则为例
proxy: {
// 在此处编写代理规则
'/api': {
target: 'http://localhost:7150',
changeOrigin: true,
rewrite: (path) => {
return path.replace(/\/api/, '')
}
}
}
只要浏览器发出的请求URL的路径中含有/api,该代理规则就会生效
2.2.2 target和changeOrigin
target要和changeOrigin结合使用,只有changeOrigin的值为true,target才会生效
以上述代理规则为例,如果浏览器发出的请求URL为/api/user/login,经过代理后,浏览器真正发出的请求URL就是http://localhost:7150/user/login
changeOrgin的作用就是改变请求URL的源(也就是域)。在上述例子中,请求URL的源被改成了http://localhost:7150(target)
2.2.3 rewrite
在配置代理规则时,我们不仅可以改变请求URL的源,还可以修改请求URL的路径
rewrite: (path) => {
return path.replace(/\/api/, '')
}
path.replace()方法接收两个参数
- 第一个参数可以填写正则表达式,也可以填写纯字符串,代表要匹配的路径
- 第二个参数填写字符串,代表匹配到路径后,要将匹配到的路径替换成什么样的内容
在上述例子中,浏览器发出的请求URL为/api/user/login,因为请求URL的路径中含有/api,经过代理后,路径中的/api被替换成了空字符串。最后,浏览器真正发出的请求URL就是http://localhost:7150/user/login
2.3 测试代理是否生效
2.3.1 搭建后台服务器
我们用SpringBoot搭建一个后台服务器,编写一个简单的实体类和一个简单的Controller,在 7150 端口上启动,用于接收前端发送的请求
LoginDto.java
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class LoginDto {
private String username;
private String password;
@Override
public String toString() {
return "LoginDto{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
UserController.java
import cn.edu.scau.pojo.LoginDto;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
@PostMapping("/login")
public String login(@RequestBody LoginDto loginDto) {
System.err.println("登录信息:" + loginDto);
return "已成功接收到登录信息:" + loginDto;
}
}
2.3.2 前端发起请求
我们用axios发起请求
axios.post('/api/user/login', {
username: 'admin',
password: '123456'
}).then((response) => {
console.log('后端返回的数据:', response.data)
})
后台服务器成功收到数据

前端也成功收到后台服务器返回的信息

2.4 配置多个代理规则
配置多个代理规则时,代理规则之间需要用逗号分隔
proxy: {
// 在此处编写代理规则
'/api': {
target: 'http://localhost:7150',
changeOrigin: true,
rewrite: (path) => {
return path.replace(/\/api/, '')
}
},
'/dev': {
target: 'http://localhost:7150',
changeOrigin: true,
rewrite: (path) => {
return path.replace(/\/dev/, '')
}
}
}
2.5 代理举例
为了方便演示,使用黑马程序员提供的接口(大家多多支持黑马程序员的课程)
接口文档地址:B站-AJAX和黑马头条-数据管理平台
https://apifox.com/apidoc/shared-1b0dd84f-faa8-435d-b355-5a8a329e34a8/api-87683404
2.5.1 例一
代理规则
'/areaList': {
target: 'http://hmajax.itheima.net',
changeOrigin: true,
rewrite: (path) => {
return path.replace(/\/areaList/, '/api/area')
}
}
前端发起的请求URL
axios.get('/areaList?pname=广东省&cname=广州市')
.then((response) => {
console.log('广东省广州市的地区列表:', response.data)
})
经过代理后真正发出的请求URL
http://hmajax.itheima.net/api/area?pname=广东省&cname=广州市
返回的数据
{
"message": "获取地区县成功",
"list": [
"荔湾区",
"越秀区",
"海珠区",
"天河区",
"白云区",
"黄埔区",
"番禺区",
"花都区",
"南沙区",
"萝岗区",
"增城市",
"从化市"
]
}
2.5.2 例二
代理规则
'/newsList': {
target: 'http://hmajax.itheima.net',
changeOrigin: true,
rewrite: (path) => {
return path.replace(/\/newsList/, '/api/news')
}
}
前端发起的请求URL
axios.get('/newsList')
.then((response) => {
console.log('新闻列表:', response.data)
})
经过代理后真正发出的请求URL
http://hmajax.itheima.net/api/news
返回的数据
{
"message": "获取新闻列表成功",
"data": [
{
"id": 1,
"title": "5G渗透率持续提升,创新业务快速成长",
"source": "新京报经济新闻",
"cmtcount": 58,
"img": "http://ajax-api.itheima.net/images/0.webp",
"time": "2222-10-28 11:50:28"
},
{
"id": 5,
"title": "为什么说中美阶段性协议再近一步,读懂周末的这些关键信息",
"source": "澎湃新闻",
"cmtcount": 131,
"img": "http://ajax-api.itheima.net/images/4.webp",
"time": "2222-10-24 09:08:34"
},
{
"id": 6,
"title": "阿根廷大选结果揭晓:反对派费尔南德斯有话要说",
"source": "海外网",
"cmtcount": 99,
"img": "http://ajax-api.itheima.net/images/5.webp",
"time": "2222-10-23 17:41:15"
},
{
"id": 8,
"title": "LV母公司当年史上最大并购:报价145亿美元购Tiffany",
"source": "澎湃新闻",
"cmtcount": 119,
"img": "http://ajax-api.itheima.net/images/7.webp",
"time": "2222-10-22 03:59:44"
},
{
"id": 9,
"title": "黄峥当年1350亿蝉联80后白手起家首富:1年中财富每天涨1个亿",
"source": "胡润百富",
"cmtcount": 676,
"img": "http://ajax-api.itheima.net/images/8.webp",
"time": "2222-10-21 06:19:37"
}
]
}
2.5.3 例三
代理规则
'/weather': {
target: 'http://hmajax.itheima.net',
changeOrigin: true,
rewrite: (path) => {
return path.replace(/\/weather/, '/api/weather')
}
}
前端发起的请求URL
axios.get('/weather?city=110100')
.then((response) => {
console.log('北京市的天气信息:', response.data)
})
经过代理后真正发出的请求URL
http://hmajax.itheima.net/api/weather?city=110100
返回的数据
{
"code": 10000,
"message": "查询天气成功",
"data": {
"date": "2024-05-19",
"area": "北京市",
"dateShort": "05月19日",
"dateLunar": "四月十二",
"temperature": "23",
"weather": "晴",
"weatherImg": "https://hmajax.itheima.net/weather/qingline.png",
"windPower": "4级",
"windDirection": "东风",
"psPm25Level": "良",
"psPm25": "94",
"todayWeather": {
"humidity": "65.0",
"sunriseTime": "04:56",
"sunsetTime": "19:27",
"ultraviolet": "弱",
"weather": "小雨",
"temDay": "28",
"temNight": "17"
},
"dayForecast": [
{
"date": "05月19日",
"temDay": "28",
"weather": "小雨",
"temNight": "17",
"windPower": "3-4级",
"dateFormat": "今天",
"weatherImg": "https://hmajax.itheima.net/weather/xiaoyu.png",
"windDirection": "东风"
},
{
"date": "05月20日",
"temDay": "28",
"weather": "晴",
"temNight": "17",
"windPower": "<3级",
"dateFormat": "明天",
"weatherImg": "https://hmajax.itheima.net/weather/qing.png",
"windDirection": "南风"
},
{
"date": "05月21日",
"temDay": "31",
"weather": "晴",
"temNight": "20",
"windPower": "<3级",
"dateFormat": "后天",
"weatherImg": "https://hmajax.itheima.net/weather/qing.png",
"windDirection": "南风"
},
{
"date": "05月22日",
"temDay": "33",
"weather": "多云",
"temNight": "21",
"windPower": "3-4级",
"dateFormat": "周三",
"weatherImg": "https://hmajax.itheima.net/weather/duoyun.png",
"windDirection": "西南风"
},
{
"date": "05月23日",
"temDay": "32",
"weather": "多云",
"temNight": "18",
"windPower": "3-4级",
"dateFormat": "周四",
"weatherImg": "https://hmajax.itheima.net/weather/duoyun.png",
"windDirection": "东风"
},
{
"date": "05月24日",
"temDay": "28",
"weather": "多云",
"temNight": "18",
"windPower": "<3级",
"dateFormat": "周五",
"weatherImg": "https://hmajax.itheima.net/weather/duoyun.png",
"windDirection": "西南风"
},
{
"date": "05月25日",
"temDay": "28",
"weather": "阴",
"temNight": "18",
"windPower": "<3级",
"dateFormat": "周六",
"weatherImg": "https://hmajax.itheima.net/weather/yin.png",
"windDirection": "东南风"
}
]
}
}
2.5.4 例四
代理规则
'/comment': {
target: 'https://hmajax.itheima.net',
changeOrigin: true,
rewrite: (path) => {
return path.replace(/\/comment/, '/api/addcmt')
}
}
前端发起的请求URL
axios.post('/comment', {
username: "老李",
content: "大家好!"
}).then((response) => {
console.log('评论成功:', response.data)
})
经过代理后真正发出的请求URL
https://hmajax.itheima.net/api/addcmt
返回的数据

3. 在Vue3项目(由Vite构建)中实现代理的原理
在 Vite 项目中,vite.config.js 文件中的代理配置(server.proxy)主要用于开发环境(vite dev),它通过 Vite 的内置开发服务器来实现请求的代理
然而,在生产环境中,Vite 的开发服务器不会被使用,因此代理配置在打包后的代码中不会生效
3.1 代理配置的作用范围(仅适用于开发环境,在生产环境中不会生效)
server.proxy是专门为开发环境设计的功能,用于解决开发阶段的跨域问题server.proxy依赖于 Vite 的开发服务器(基于 Node.js 的 HTTP 服务)来拦截和转发请求- 在生产环境中,Vite 打包后的代码通常会被部署到一个静态文件服务器(如 Nginx、Apache 或其他 CDN),而这些服务器并不具备 Vite 开发服务器的功能,因此代理配置无法生效
3.2 Vite 的运行机制
- Vite 是一个现代化的前端构建工具,它的开发服务器(
vite dev)是基于 Node.js 实现的 - 在开发阶段,Vite 使用 Node.js 来启动一个本地开发服务器,并提供热更新(HMR)、代理配置等功能
- 因此,在开发环境中,必须安装 Node.js 才能运行 Vite 的开发服务器
3.3 生产环境的运行机制
- 在生产环境中,前端代码是静态资源(HTML、CSS、JavaScript 等),它们由 Web 服务器或反向代理服务器(如 Nginx)提供服务
- 如果需要在生产环境中实现类似的代理功能,则需要在 Web 服务器或反向代理服务器上进行配置
3.4 在生产环境中使用 Nginx 配置代理
在生产环境中,可以通过配置 Nginx 或其他反向代理服务器来实现类似的功能
server {
listen 80;
server_name your-domain.com;
# 静态资源路径
location / {
root /path/to/your/dist; # 前端打包后的文件目录
index index.html;
try_files $uri /index.html; # 支持前端路由
}
# API 代理配置
location /api {
proxy_pass http://backend-server-address; # 后端服务地址
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;
}
}
通过这种方式,Nginx 会在生产环境中接管请求,并将 /api 的请求转发到后端服务
3.5 npm run dev 指令的工作原理
npm run dev 是一个常见的命令,用于启动项目的开发环境,它的原理涉及多个部分,包括 npm、package.json 文件中的脚本配置以及底层工具(如 Vite、Webpack 等)的运行机制
建议观看以下内容之前先阅读我的另一篇博文:package.json文件详解(软件的版本号规则、package.json文件有什么用、package.json文件核心字段讲解、package-lock.json文件、脚本命令的工作原理)
3.5.1 npm run 的作用
npm run是 npm 提供的一个命令,用于执行定义在package.json文件中的脚本- 具体来说,
npm run <script-name>会查找package.json中scripts字段下的对应脚本,并执行它
{
"scripts": {
"dev": "vite"
}
}
运行 npm run dev 时,npm 会执行 vite 命令
3.5.2 npm run dev 指令的执行流程
3.5.2.1 查找 package.json 文件中的 scripts 配置
-
当运行
npm run dev时,npm 会首先读取项目根目录下的package.json文件 -
它会在
scripts字段中查找名为dev的脚本。例如:
{
"scripts": {
"dev": "vite"
}
}
这里的 dev 脚本定义了要执行的命令:vite
3.5.2.2 解析并执行脚本
- npm 会解析
dev脚本的内容(这里是vite),并尝试找到对应的可执行文件 - 如果
vite是一个全局安装的工具,npm 会直接调用它 - 如果
vite是项目本地依赖(通常在node_modules/.bin目录下),npm 会优先使用本地版本
3.5.2.3 启动开发服务器
在使用 Vite 的情况下,vite 命令会启动 Vite 的开发服务器
开发服务器的主要功能包括:
- 提供静态资源服务(HTML、CSS、JavaScript 等)
- 实现模块热更新(HMR,Hot Module Replacement),使得代码修改后无需刷新整个页面
- 处理代理配置(通过
server.proxy) - 支持 ES 模块的按需加载
3.5.3 npm run dev 指令的执行步骤
- npm 查找脚本:
- npm 读取
package.json,找到scripts.dev的值为vite
- npm 读取
- 解析命令:
- npm 在项目的
node_modules/.bin目录中找到vite可执行文件
- npm 在项目的
- 启动 Vite 开发服务器:
- Vite 启动开发服务器,默认监听
http://localhost:5173 - 开发服务器开始处理浏览器的请求,包括静态资源、模块加载和代理配置
- Vite 启动开发服务器,默认监听
- 浏览器访问:
- 打开浏览器访问
http://localhost:5173,Vite 开发服务器会提供 HTML 文件,并动态加载模块
- 打开浏览器访问
- 热更新:
- 当你修改代码时,Vite 会检测到变化并通知浏览器更新相应的模块
3.6 Vite 开发服务器的工作原理
Vite 是一个现代化的前端构建工具,其开发服务器的设计与传统工具(如 Webpack)有很大不同
3.6.1 原生 ES 模块支持
- Vite 利用了现代浏览器对原生 ES 模块的支持(
<script type="module">) - 在开发环境中,Vite 不会对代码进行打包,而是直接将源码作为模块提供给浏览器
- 浏览器通过 HTTP 请求动态加载模块,Vite 的开发服务器负责拦截这些请求并返回对应的模块内容
3.6.2 按需编译
- 当浏览器请求某个模块时,Vite 会根据需要对该模块进行即时编译(例如将 TypeScript 编译为 JavaScript)
- 这种按需编译的方式避免了传统工具(如 Webpack)在开发阶段进行全量打包的性能瓶颈
3.6.3 热更新(HMR)
- Vite 的开发服务器内置了 HMR 功能
- 当修改代码时,Vite 会通知浏览器只更新发生变化的模块,而无需刷新整个页面
- 这大大提高了开发效率,尤其是在大型项目中
3.6.4 代理功能
- Vite 的开发服务器支持通过
server.proxy配置代理请求 - 例如,可以将
/api的请求代理到后端服务器,从而解决开发阶段的跨域问题
3.7 代理规则的工作机制
在 Vite 的开发环境中,server.proxy 配置的作用是通过 Vite 开发服务器(基于 Node.js)拦截特定的请求,并将这些请求转发到目标服务器
3.7.1 请求必须发送到开发服务器
- Vite 的代理功能依赖于开发服务器的运行
- 如果直接向目标服务器发送请求(例如,直接访问
http://target-server.com/api),那么请求不会经过 Vite 开发服务器,因此代理规则不会生效
运行 npm run dev 指令之后可以看到开发服务器占用了哪个端口(默认占用 5173 端口)

3.7.2 代理规则匹配请求路径
- 代理规则会根据请求的路径(如
/api)进行匹配 - 如果请求发送到开发服务器(例如
http://localhost:5173/api),并且路径符合代理规则(如/api),那么开发服务器会拦截该请求并将其转发到目标服务器
3.7.3 确保开发环境使用代理
在开发环境中,前端代码应该始终通过开发服务器发送请求。例如:
fetch('/api/users')
.then(response => response.json())
.then(data => console.log(data))
这里的 /api/users 是相对路径,默认会发送到开发服务器(http://localhost:5173/api/users),从而触发代理规则
4. 补充:为什么直接运行vite指令报错,而运行 npm run dev 指令却正常
如果你在项目的根目录下直接运行 vite 命令,大概率会遇到以下报错

vite : 无法将“vite”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确
,然后再试一次。
所在位置 行:1 字符: 1
+ vite
+ ~~~~
+ CategoryInfo : ObjectNotFound: (vite:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
为什么直接运行 vite 指令报错,而运行 npm run dev 指令却正常呢,这两个指令本质上不是一样的吗
建议观看以下内容之前先阅读我的另一篇博文:package.json文件详解(软件的版本号规则、package.json文件有什么用、package.json文件核心字段讲解、package-lock.json文件、脚本命令的工作原理)
4.1 直接运行 vite 失败的原因
4.1.1 vite 没有全局安装
- 如果你直接在终端中运行
vite,系统会尝试查找一个名为vite的可执行命令 - 如果
vite没有全局安装(即没有通过npm install -g vite安装到全局环境),系统就找不到这个命令,从而报错
4.1.2 本地安装的 vite 不在 PATH 中
- 在大多数项目中,
vite是作为项目的开发依赖安装的(通过npm install vite --save-dev) - 这种情况下,
vite被安装到了项目的node_modules/.bin目录下,而不是全局环境 - 系统的
PATH环境变量通常不会包含项目的node_modules/.bin目录,因此直接运行vite会失败
4.1.3 手动指定路径运行
如果不想使用 npm run dev,也可以手动指定 vite 的路径来运行它。例如:
在 Linux/MacOS 上
./node_modules/.bin/vite
在 Windows 上:
.\node_modules\.bin\vite
这样可以直接调用本地安装的 vite
4.2 npm run dev 为什么可以运行(npm run 自动添加 node_modules/.bin 到 PATH)
当运行 npm run <script> 时,npm 会自动将项目的 node_modules/.bin 目录临时添加到系统的 PATH 环境变量
这意味着,在 npm run dev 执行期间,系统能够找到 node_modules/.bin/vite,即使它没有全局安装
上面说到“运行 npm run <script> 时,npm 会自动将项目的 node_modules/.bin 目录临时添加到系统的 PATH 环境变量中”
但 npm 并不是去修改系统的全局环境变量(比如你在 Windows 的“系统属性”或 Linux/macOS 的 ~/.bashrc、~/.zshrc 文件中设置的 PATH),如果那样做,会非常危险且混乱
当运行 npm run dev 指令时,npm 会创建一个新的子进程(通常是 shell,比如 Linux/macOS 下的 sh 或 Windows 下的 cmd.exe),脚本命令(比如 vite)是在这个子进程中执行的,而不是在当前的终端主进程中执行的
在启动这个子进程之前,npm 会做一件关键的事情:构建一个全新的 PATH 环境变量,并把全新的 PATH 环境变量只传递给这个子进程
上述只是 npm run dev 指令执行过程的简单描述,具体的执行过程可以参考我的另一篇博文:package.json文件详解(软件的版本号规则、package.json文件有什么用、package.json文件核心字段讲解、package-lock.json文件、脚本命令的工作原理)
4.3 为什么 npm run dev 指令更常用
4.3.1 无需全局安装工具
- 通过
npm run dev,你可以直接使用项目本地安装的工具(如vite),而不需要全局安装 - 这种方式更符合现代前端开发的最佳实践,避免了不同项目之间的版本冲突
4.3.2 跨平台兼容性
npm run提供了一种跨平台的方式运行脚本,无论是在 Linux、macOS 还是 Windows 上,都可以正常工作- 如果直接运行
./node_modules/.bin/vite,在 Windows 上需要改为.\node_modules\.bin\vite,这会导致脚本不够通用
4.3.3 简化开发流程
- 开发者只需要记住
npm run dev,而不需要关心底层工具的具体路径或安装方式 - 这使得团队协作更加高效,减少了环境配置的复杂性
4.4 如何直接运行 vite(了解即可,不推荐使用)
如果你确实希望直接运行 vite,可以采取以下方法:
4.4.1 全局安装 vite
npm install -g vite
全局安装后,你可以在任何地方直接运行 vite 命令
4.4.2 修改系统的环境变量
将 node_modules/.bin 添加到系统的 PATH 环境变量中,就可以直接运行 vite 了
更多推荐
所有评论(0)