Astro+Svelte订阅表单提交API报400错误:邮箱无法传递
Astro + Svelte订阅表单400错误:前端邮箱无法传递到API路由
开发订阅表单时,提交返回400错误。API路由硬编码邮箱时功能正常,但前端邮箱无法传递到路由。具体情况如下:
配置概述
- 客户端(Svelte组件):表单收集邮箱,通过
fetch提交到/api/sendEmail.json,请求头设为Content-Type: application/json,请求体用JSON.stringify({ email: email })序列化。 - 服务端(API路由):通过
await request.json()解析请求体,但收到的请求体为空字符串。
已尝试的排查步骤
- 验证表单提交触发正常,
email变量更新符合预期; - 浏览器开发者工具确认请求方法为POST、Content-Type头正确、请求负载含预期JSON;
- 在
handleSubmit中硬编码email,排除变量本身问题; - 检查全局fetch拦截器,确保请求未被修改;
- 测试其他API端点,排除端点特定问题;
- 简化服务端逻辑,打印原始请求体和头。
代码片段
Svelte表单组件
<script lang="ts"> import { onMount } from "svelte"; import Reload from "svelte-radix/Reload.svelte"; import { Button } from "@/components/ui/button/index"; import { toast } from "svelte-sonner"; let email = ""; let loading = false; let formStatus = ""; const handleSubmit = async (e) => { e.preventDefault(); loading = true; formStatus = ""; try { console.log(email); const response = await fetch("/api/sendEmail.json", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ email: email, }), }); if (response.ok) { const data = await response.json(); if (data.message === "Subscription successful") { formStatus = "success"; toast("Subscription successful", { description: "You have been subscribed to the newsletter.", }); } else { formStatus = "error"; toast("Subscription failed", { description: "There was an error subscribing you to the newsletter.", }); } } else { formStatus = "error"; toast("Subscription failed", { description: "There was an error subscribing you to the newsletter.", }); } } catch (err) { console.error(err); formStatus = "error"; toast("Subscription failed", { description: "There was an error subscribing you to the newsletter.", }); } finally { loading = false; } }; // Log any errors during component initialization onMount(() => { window.onerror = function (msg, url, lineNo, columnNo, error) { console.error( "Error occurred:", msg, "URL:", url, "Line:", lineNo, "Column:", columnNo, "Error object:", error ); }; }); </script> <form on:submit={handleSubmit}> <div class="overflow-hidden py-16 sm:py-24 lg:py-32"> <div class="max-w-2xl gap-x-8 gap-y-16 lg:max-w-none flex place-content-center mx-auto" > <div class="max-w-xl lg:max-w-lg"> <h2 class="text-3xl font-bold tracking-tight text-white sm:text-4xl"> Subscribe to my Newsletter. </h2> <p class="mt-4 text-lg leading-8 text-gray-300"> A Newsletter for Designers, Developers and Humans. </p> <div class="mt-6 flex max-w-md gap-x-4"> <label for="email-address" class="sr-only"> Email address </label> <input name="email" type="email" autocomplete="email" required class="min-w-0 flex-auto rounded-md border-0 bg-white/5 px-3.5 py-2 text-white shadow-sm ring-1 ring-inset ring-white/10 focus:ring-2 focus:ring-inset focus:ring-neutral-500 sm:text-sm sm:leading-6" placeholder="Enter your email" bind:value={email} /> <Button type="submit" class="flex-none rounded-md bg-neutral-200 px-3.5 py-2.5 text-sm font-semibold text-neutral-900 shadow-sm hover:bg-neutral-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:neutral-indigo-500" > {#if loading} <Reload class="mr-2 h-4 w-4 animate-spin" /> {/if} Subscribe </Button> </div> </div> </div> </div> </form>
Astro API路由
import type { APIRoute } from "astro"; import { Resend } from "resend"; const resend = new Resend(import.meta.env.RESEND_API_KEY); export const POST: APIRoute = async ({ request }) => { try { const { email } = await request.json(); console.log(email); const send = await resend.contacts.create({ email, firstName: "", lastName: "", unsubscribed: false, audienceId: import.meta.env.RESEND_AUDIENCE_ID, }); if (send.data) { return new Response( JSON.stringify({ message: "Subscription successful" }), { status: 200, statusText: "OK", } ); } else { return new Response(JSON.stringify({ message: "Subscription failed" }), { status: 500, statusText: "Error", }); } } catch (error) { console.error("Error parsing request body:", error); return new Response(JSON.stringify({ message: "Invalid request body" }), { status: 400, statusText: "Bad Request", }); } };
解决方案
1. 手动读取原始请求体解析
Astro的request.json()可能存在解析异常,尝试直接读取原始文本再手动解析,同时打印原始内容确认:
export const POST: APIRoute = async ({ request }) => { try { const rawBody = await request.text(); console.log("Raw request body:", rawBody); const { email } = rawBody ? JSON.parse(rawBody) : {}; if (!email) { return new Response(JSON.stringify({ message: "Email is required" }), { status: 400, }); } const send = await resend.contacts.create({ email, firstName: "", lastName: "", unsubscribed: false, audienceId: import.meta.env.RESEND_AUDIENCE_ID, }); if (send.data) { return new Response( JSON.stringify({ message: "Subscription successful" }), { status: 200 } ); } else { return new Response(JSON.stringify({ message: "Subscription failed" }), { status: 500, }); } } catch (error) { console.error("Error processing request:", error); return new Response(JSON.stringify({ message: "Invalid request body" }), { status: 400, }); } };
2. 明确指定请求头字符编码
浏览器可能自动添加编码信息导致解析问题,修改fetch请求头:
headers: { "Content-Type": "application/json; charset=utf-8", },
3. 检查Astro中间件是否消耗请求体
如果项目使用了中间件,确保中间件没有提前调用request.text()或request.json(),否则后续API路由无法再次读取请求体。
4. 改用FormData提交(备选方案)
若JSON方式始终异常,切换为FormData提交:
客户端修改:
const formData = new FormData(); formData.append("email", email); const response = await fetch("/api/sendEmail.json", { method: "POST", body: formData, });
服务端修改:
const formData = await request.formData(); const email = formData.get("email") as string;
5. 验证环境变量配置
确认import.meta.env.RESEND_AUDIENCE_ID和RESEND_API_KEY加载正常,避免因环境变量问题引发间接错误。
内容的提问来源于stack exchange,提问作者Pruthivi Thejan
相关产品推荐
相关产品推荐

