先搞清楚一个事实:浏览器根本不关心你代码写得对不对
跨域报错最让人崩溃的地方在于:请求发出去了,服务端也响应了,数据也返回了,但浏览器就是不给你。
这不是你的代码有问题,也不是后端接口挂了。浏览器的同源策略在 Fetch 或 XHR 的响应阶段 介入,检查响应头里有没有 Access-Control-Allow-Origin。如果这个头缺失或不匹配,响应体直接丢弃,前端拿到一个空壳。
所以跨域的本质是:服务端必须通过响应头告诉浏览器"我允许这个源访问" 。后端不改,前端怎么折腾都没用。
下面按实际开发场景说人话。
CORS:唯一正确的生产级方案
CORS 不是"绕过"跨域,它是 W3C 标准定义的跨域正式通道 。服务端设置响应头,浏览器验证放行。
关键响应头只有几个:
http
yaml
Access-Control-Allow-Origin: https://your-frontend.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
最容易踩的坑在 Preflight(预检请求) 。当你的请求满足以下任一条件时,浏览器会先发一个 OPTIONS 探路请求:
- 方法不是 GET/HEAD/POST
Content-Type是application/json(而不是text/plain)- 带了自定义请求头(比如
Authorization)
这个 OPTIONS 请求不携带你的业务参数,服务端必须返回 204 或 200,并附带上述 CORS 响应头。如果后端只处理了 POST 没处理 OPTIONS,预检就挂了,正式请求根本发不出去。
带 Cookie 的场景更严格 :Access-Control-Allow-Origin 不能是 *,必须写明确的前端域名,同时 Access-Control-Allow-Credentials: true。
开发阶段的代理:不是解决跨域,是消灭跨域
CORS 需要后端配合,但开发时后端经常还没部署、或懒得给你配 CORS。代理的本质是:让浏览器以为请求发给了同源的服务,再由开发服务器转发到真正的后端。
Vite 代理(当前最主流)
javascript
javascript
// vite.config.js
export default {
server: {
proxy: {
'/api': {
target: 'http://localhost:3001',
changeOrigin: true,
rewrite: path => path.replace(/^/api/, '')
}
}
}
}
前端代码里写 fetch('/api/users'),Vite Dev Server 收到后转发到 localhost:3001/users。浏览器全程只和 localhost:5173 打交道,同源,无跨域。
Nginx 反向代理(生产环境标准答案)
开发用 Vite,生产用 Nginx,这是目前最常见的前后端分离部署方式:
nginx
bash
location /api/ {
proxy_pass http://backend-service:3001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
前端静态文件和 /api 请求都在同一个 Nginx 域名下,浏览器视角全是同源。
代理方案的真正价值:不仅消灭了跨域,还隐藏了后端真实地址,顺便解决了 HTTPS 混合内容问题。
命令行禁用 Web 安全:能用,但要知道自己在干什么
有些场景下,你就是不想动后端,也不想配代理。比如临时调一个第三方 API,或者本地调试一个别人的线上页面。
Chrome 的 --disable-web-security 可以直接关掉同源策略。必须配合独立的用户数据目录,否则会和你的日常浏览器配置冲突:
bash
python
# macOS
open -n -a "Google Chrome" --args --disable-web-security --user-data-dir=/tmp/chrome_dev
# Windows
"C:\Program Files\Google\Chrome\Application\chrome.exe" --disable-web-security --user-data-dir=C:\ChromeDevUserData
启动后地址栏下方会出现黄色警告条"你使用的是不受支持的命令标记",说明配置生效了。
这件事的边界很清楚 :只在你自己的机器上,只用于调试。不要把它当成开发流程的一部分,更不要教给团队里不了解原理的人。--user-data-dir 的意义是隔离,别用你的主浏览器配置跑这个模式。
不该再用的方案
JSONP 可以直接划掉了。 它只能发 GET 请求,没有错误处理,有 XSS 风险,唯一的历史价值是让你理解 <script> 标签不受同源限制这件事。2026 年没有任何理由在新项目里用 JSONP。
document.domain 也已经废弃。 之前用于同一主域下子域间通信,现在浏览器逐步移除支持,不要再写进技术方案里。
WebSocket 本身不受同源策略约束 ,但服务端必须自己校验 Origin 头,否则等于把安全责任从浏览器转移给了开发者。如果你用 WebSocket 做实时通信,跨域不是问题,鉴权才是。
选型建议
一张表说清楚:
| 场景 | 方案 |
|---|---|
| 前后端联调,后端已部署 | 让后端配 CORS |
| 后端还没好 / 不想打扰后端 | Vite/Webpack 代理 |
| 生产环境前后端分离 | Nginx 反向代理 |
| 临时调第三方 API,只想快速验证 | Chrome 禁用安全模式 |
| 微前端 / iframe 通信 | postMessage |
核心判断逻辑:能控制后端就上 CORS,不能控制就用代理,都不想动就开一个隔离的调试浏览器。别把临时方案塞进生产代码里。