Skip to main content
Benny的老巢 Logo
Overview

Cloudflare Workers CORS 跨域问题排查与解决

January 8, 2026
2 min read

你现在看到的是两个完全不同的问题叠加在一起。

  • 真正导致表单提交失败的是:CORS
  • CSP 报错是浏览器插件造成的”干扰信息”,可以忽略

先给结论

表单失败的根因是:Worker 没有正确处理 CORS 预检请求(OPTIONS)。只要把 CORS + OPTIONS 补齐,fetch failed 会立刻消失。

日志逐条解释

这个 CSP 报错【不是你的问题】

Executing inline script violates the following Content Security Policy...
chrome-extension://a05f635f...

这是浏览器扩展(插件)注入脚本被 CSP 拦截。

  • 来自 chrome-extension://...
  • 和你的 Astro / Worker / 表单 无关
  • 不影响接口请求

可以完全忽略。

这个才是致命错误【真正原因】

Access to fetch at 'https://xxx.workers.dev/api/contact'
from origin 'http://localhost:4321'
has been blocked by CORS policy:
Response to preflight request doesn't pass access control check:
No 'Access-Control-Allow-Origin' header

关键信息:

  • 浏览器发起了 OPTIONS 预检请求
  • Worker 没有返回 CORS 头
  • 浏览器直接拦截 → ERR_FAILED

为什么会有 OPTIONS 预检?

因为你满足了以下任意条件(你全中了):

  • Content-Type: application/json
  • POST 请求
  • 跨域(localhost → workers.dev)

浏览器 一定会先发 OPTIONS

你现在的 Worker 一定缺了这段逻辑

if (request.method === 'OPTIONS') {
// 你现在没有正确处理
}

直接给你一份生产可用的 Worker 模板

你可以整体替换当前 Worker 代码。

const corsHeaders = {
'Access-Control-Allow-Origin': 'http://localhost:4321', // 本地开发
// 生产环境可以换成你的域名
// 'https://yourdomain.com'
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
}
export default {
async fetch(request, env) {
// 1️⃣ 处理 CORS 预检
if (request.method === 'OPTIONS') {
return new Response(null, {
status: 204,
headers: corsHeaders,
})
}
// 2️⃣ 只允许 POST
if (request.method !== 'POST') {
return new Response('Method Not Allowed', {
status: 405,
headers: corsHeaders,
})
}
// 3️⃣ 解析数据
const { name, email, phone, message } = await request.json()
if (!name || !email || !message) {
return new Response(
JSON.stringify({ error: 'Missing fields' }),
{
status: 400,
headers: {
...corsHeaders,
'Content-Type': 'application/json',
},
}
)
}
// 4️⃣ 调用 Resend
const res = await fetch('https://api.resend.com/emails', {
method: 'POST',
headers: {
'Authorization': `Bearer ${env.RESEND_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
from: 'Website Contact <no-reply@YOUR_VERIFIED_DOMAIN.com>',
subject: 'New Contact Message',
html: `
<p><b>Name:</b> ${name}</p>
<p><b>Email:</b> ${email}</p>
<p><b>Phone:</b> ${phone || '-'}</p>
<p><b>Message:</b> ${message}</p>
`,
}),
})
if (!res.ok) {
const err = await res.text()
return new Response(err, {
status: 500,
headers: corsHeaders,
})
}
// 5️⃣ 成功返回
return new Response(
JSON.stringify({ success: true }),
{
headers: {
...corsHeaders,
'Content-Type': 'application/json',
},
}
)
},
}

Astro 前端这边你不用改(基本是对的)

await fetch('https://icy-truth-83e1.bennyxqg.workers.dev/api/contact', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
})

没问题,CORS 是后端的责任。

常见优化(等你跑通后再做)

允许多个来源(开发 + 生产)

const origin = request.headers.get('Origin')
const allowed = ['http://localhost:4321', 'https://yourdomain.com']
if (allowed.includes(origin)) {
corsHeaders['Access-Control-Allow-Origin'] = origin
}

临时调试(不推荐生产)

'Access-Control-Allow-Origin': '*'

你现在的状态总结

项目状态
Worker 部署
API 路由
Resend 调用
CORS❌ → 已定位
CSP 插件报错⚠️ 可忽略

你现在已经是”只差最后一道门”。