vue前端解决跨域的三种方法 - 完整解决方案与实战教程

在前后端分离的开发模式下,Vue 项目本地跑在 http://localhost:8080,而后端 API 部署在 http://api.example.com 或另一个端口上,浏览器控制台立刻抛出红色报错:Access to XMLHttpRequest at 'xxx' from origin 'xxx' has been blocked by CORS policy。这是每一位前端工程师都会踩的坑,尤其在联调阶段,后端接口还没上线、测试环境域名不固定、或者需要携带 Cookie 做鉴权时,跨域问题会直接卡住整个开发进度,让人误以为是接口写错了或者 axios 配置有问题。本文从实际排坑角度出发,给出三种在 Vue 前端侧解决跨域的实战方案,并附上可直接复制的完整代码。

问题现象

打开 Chrome DevTools 的 Network 面板,请求状态显示为 (failed) net::ERR_FAILEDCORS error,Console 中出现类似下面的报错:

Access to XMLHttpRequest at 'http://localhost:3000/api/user' 
from origin 'http://localhost:8080' has been blocked by CORS policy: 
No 'Access-Control-Allow-Origin' header is present on the requested resource.

典型特征包括:

原因分析

跨域的本质是浏览器的同源策略(Same-Origin Policy):协议、域名、端口三者必须完全一致。Vue 开发服务器默认运行在 localhost:8080(Vite 为 5173),而后端服务通常在不同端口或域名,因此触发跨域限制。

需要明确几个关键点:

解决方案(附完整代码)

方法一:Vue CLI / Vite 开发服务器代理(推荐开发环境使用)

原理是让开发服务器充当中间人:浏览器请求同源的 /api,开发服务器再转发到真实后端,从而绕过浏览器同源策略。

Vue CLI(webpack)配置:在项目根目录创建或修改 vue.config.js

// vue.config.js
module.exports = {
  devServer: {
    port: 8080,
    proxy: {
      // 匹配所有以 /api 开头的请求
      '/api': {
        target: 'http://localhost:3000', // 后端真实地址
        changeOrigin: true,              // 修改请求头中的 Host,伪装成同源
        ws: false,                       // 是否代理 websocket
        pathRewrite: {
          '^/api': ''                    // 去掉 /api 前缀再转发给后端
        }
      }
    }
  }
}

Vite 配置:修改 vite.config.js

// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  server: {
    port: 5173,
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }
})

前端请求时统一使用相对路径:

// axios 实例配置
import axios from 'axios'

const request = axios.create({
  baseURL: '/api',  // 走开发服务器代理
  timeout: 10000
})

export default request

排查步骤:

  1. 确认 target 地址可被浏览器直接访问(用 Postman 验证)。
  2. 修改配置文件后必须重启 dev server,热更新不会生效。
  3. 如果后端接口本身带 /api 前缀,则不要配置 pathRewrite
  4. 代理只在开发环境生效,打包后的生产环境需要 Nginx 或后端 CORS 支持。

方法二:后端配置 CORS 响应头(推荐生产环境使用)

由后端在响应中加上 CORS 相关头,浏览器看到合法头后放行。前端无需任何改动。

以 Node.js + Express 为例:

// server.js
const express = require('express')
const cors = require('cors')
const app = express()

// 允许所有来源(仅测试环境使用)
app.use(cors())

// 生产环境精细化配置
app.use(cors({
  origin: ['http://localhost:8080', 'https://www.example.com'], // 白名单
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,        // 允许携带 Cookie
  maxAge: 86400             // 预检结果缓存 24 小时
}))

app.listen(3000, () => console.log('Server running on 3000'))

如果使用 Nginx 反向代理,可在配置中加入:

location /api/ {
    proxy_pass http://backend_server/;
    add_header Access-Control-Allow-Origin $http_origin always;
    add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS' always;
    add_header Access-Control-Allow-Headers 'Content-Type, Authorization' always;
    add_header Access-Control-Allow-Credentials 'true' always;

    # 处理预检请求
    if ($request_method = OPTIONS) {
        return 204;
    }
}

前端侧需要配合的配置(携带 Cookie 时):

// axios 全局配置
axios.defaults.withCredentials = true

// 或单个请求
axios.get('/api/user', { withCredentials: true })

注意事项:

方法三:JSONP(仅限 GET,兼容老项目)

JSONP 利用 <script> 标签不受同源策略限制的特性,通过回调函数接收数据。仅适用于 GET 请求,且需要后端配合返回 JS 脚本。

// 封装一个简单的 JSONP 函数
function jsonp(url, params = {}, callbackName = 'callback') {
  return new Promise((resolve, reject) => {
    // 生成唯一回调名,避免多次请求冲突
    const cbName = `jsonp_${Date.now()}_${Math.floor(Math.random() * 1000)}`
    
    // 挂载全局回调
    window[cbName] = (data) => {
      resolve(data)
      // 清理:删除 script 标签和全局函数
      document.body.removeChild(script)
      delete window[cbName]
    }

    // 拼接参数
    const query = new URLSearchParams({ ...params, [callbackName]: cbName }).toString()
    const script = document.createElement('script')
    script.src = `${url}?${query}`
    script.onerror = () => reject(new Error('JSONP request failed'))
    document.body.appendChild(script)
  })
}

// 使用示例
jsonp('http://localhost:3000/api/user', { id: 1 })
  .then(res => console.log(res))
  .catch(err => console.error(err))

后端需返回类似:

// 后端根据 callback 参数动态包裹
callback({ "code": 0, "data": { "name": "Tom" } })

JSONP 的局限非常明显:

方案选型建议

排坑的核心思路是:先看 Network 面板确认是预检失败还是响应头缺失,再判断是开发环境还是生产环境,最后选择对应方案。切忌盲目在 axios 里加各种 header,那样只会让问题更复杂。