CORS 错误困扰你?这个决策树搞定一切
你的暂存服务器可以访问你的 API。一切正常。等上线到生产环境,突然:Access to XMLHttpRequest at 'https://api.yoursite.com' from origin 'https://yoursite.com' has been blocked by CORS policy.
代码没变。完全一样的代码。但它现在在尖叫 CORS 错误。
这里是大多数独立开发者迷失两小时的地方。
CORS 为什么存在(30秒速览)
浏览器强制执行 CORS(跨源资源共享)来阻止恶意网站窃取你的数据。如果一个流氓网站能随意访问你的 API,攻击者就能掏空钱包、窃取 PII、泄露数据库——经典噩梦。CORS 是守卫。它问:”你是谁?”
问题是:往往你就是那个请求者,守卫就是认不出你。
真正有用的决策树
在你开始乱加 headers 之前,问自己三个问题:
1. 请求跨越源了吗? - 相同协议(http vs https)?相同域名(app.site.com vs api.site.com)?相同端口(8000 vs 8001)? - 任何一个不同 → 你有 CORS 问题。都相同 → CORS 不是你的麻烦(检查控制台找真实错误)。
2. 这是 preflight 请求(OPTIONS)吗? - 浏览器在发送真实的 POST/PUT/DELETE 之前自动发送一个 OPTIONS 请求。 - 如果你的服务器不响应 OPTIONS,preflight 失败,真实请求永不出发。 - 修复:你的后端必须用正确的 headers 响应 OPTIONS。
3. 你的后端发送了对的 headers 吗?
Access-Control-Allow-Origin: https://yourfrontend.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
哪怕一个错或缺少 → CORS 被挡。没有例外。
场景 1:本地开发(端口不匹配)
前端在 http://localhost:3000,API 在 http://localhost:3001。
为什么坏了:不同端口 = 不同源。
修复:
- 选项 A:通过 Next.js rewrites 或 Vite 中间件代理请求(localhost:3000/api/... → localhost:3001/...)。零 CORS 烦恼。
- 选项 B:给后端加 Access-Control-Allow-Origin: http://localhost:3000。
专业提示:选项 A 更干净。一个域名,零 CORS。这就是为什么 Next.js API 路由存在。
场景 2:暂存 + 生产不匹配
暂存能工作是因为前端和 API 都在同一域名(子域名算跨源)。生产坏了是因为域名改了或用了 CDN。
为什么坏了:你后端的 Access-Control-Allow-Origin header 被硬编码成旧域名。
修复:让 header 动态化。
不要硬编码,而是白名单源:
const allowedOrigins = ['https://yoursite.com', 'https://staging.yoursite.com', 'http://localhost:3000'];
const origin = req.headers.origin;
if (allowedOrigins.includes(origin)) {
res.set('Access-Control-Allow-Origin', origin);
}
场景 3:第三方 API(你改不了他们的 CORS)
你从浏览器调用 Stripe、Twilio 或某个第三方 API。他们没设 Access-Control-Allow-Origin: *,你的请求就死了。
为什么坏了:他们的服务器不允许来自你域名的浏览器请求。
修复:别从浏览器调。从你的后端调。
坏:从 React 里 fetch('https://api.stripe.com/...')。
好:从 React 里 fetch('/api/stripe-charge') → 后端调 Stripe。
你的后端不受 CORS 约束。它可以和谁都说话。
场景 4:凭证和 Cookie
你在 fetch 里用 credentials: 'include' 发送 auth token,但 API 没设 Access-Control-Allow-Credentials: true。
为什么坏了:浏览器阻止带凭证的请求,除非服务器明确说可以。
修复:加上 header,把 Access-Control-Allow-Origin 改成具体的(不是 *)。
如果用 Access-Control-Allow-Origin: * 搭配凭证,浏览器会拒绝。似乎矛盾,但这是故意的。
调试清单
- 打开开发者工具 Network 标签。找到失败的请求。
- 点击它。看 Response Headers。有
Access-Control-Allow-Origin吗? - 它包括你前端的源吗?
- 是 OPTIONS 请求吗?检查 200 响应有没有 CORS headers。
- 请求是 POST/PUT/DELETE 且带自定义 headers 或
Content-Type: application/json吗?确保Access-Control-Allow-Headers包含它们。
缺一个 header = 请求被挡。
什么时候会变复杂
CORS 在理论上很简单。实际上,配置不当的 CORS 会在生产部署、暂存/生产分歧、第三方集成中闹鬼。你会在不同的代码路径上诊断三次同一个问题,每次根本原因都不同。
如果你的 API 跨越多个服务、多个团队或动态生成的源,CORS 卫生就成了一个大项目。如果在生产里搞砸,用户看得见——不是安静的 bug,而是坏掉的功能。
这就是在初期搞对后端架构的重要性(加上认证、错误处理、版本管理)能为你省掉多少小时的救火。无论你是自己造 API 还是找专家帮忙,在一开始就把 CORS 搞对(连同整个栈)会节省大量时间。
如果你在开发一个涉及敏感用户数据或外部服务集成的产品,而 CORS 总是咬你一口,可能是时候从更整体的角度思考你的后端策略了。Trove Deck Solution 和 SaaS 创始人及独立开发者合作,设计不会坏掉的后端——API 安全、CORS、认证、整个栈——并正确上线。来聊聊吧。