你现在看到的是两个完全不同的问题叠加在一起。
- 真正导致表单提交失败的是: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/jsonPOST请求- 跨域(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 插件报错 | ⚠️ 可忽略 |
你现在已经是”只差最后一道门”。