svelteastrojsresend.com

Error on Form Submit to API Route - Astro and Svelte


I'm using Astro and Svelte to build a Newsletter Form and on Form Submit it returns an 400 Error. When I hardcode the email in the API Route it works. However the email does not pass into the route.

Here's a brief overview of my setup:

Client-Side (Svelte Component): I'm using a form to collect an email address and submitting it via a fetch request to a server-side API endpoint. The request is set up with the Content-Type header set to application/json, and the body is stringified using JSON.stringify({ email: email }). Server-Side (API Route): The API route is designed to parse the request body using await request.json(). However, despite the client-side setup appearing correct, the server receives an empty string for the request body. I've tried several troubleshooting steps, including:

Verifying that the form submission is correctly triggered and that the email variable is updated as expected. Inspecting the network request using browser developer tools to ensure the request method is POST, the Content-Type header is set correctly, and the request payload contains the expected JSON data. Hardcoding the email variable in the handleSubmit function to ensure the issue is not with the variable itself. Checking for global fetch interceptors and ensuring the request is not being modified. Testing with a different API endpoint to rule out issues specific to the endpoint. Simplifying the server-side handling to log the raw request body and headers.

Here is the form

 <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>

and Here is the API Route

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",
    });
  }
};

can you help me Fix this!?


Solution

  • The reason for this behavior was not adding

    export const prerender = false;
    

    to the sendEmail.json.ts while the config is using "hybrid" rendering in Astro.

    // https://astro.build/config
    export default defineConfig({
      site: "https://pruthivithejan.me",
      output: "hybrid",
      // ...
    

    You can check this closed issue https://github.com/coding-in-public/astro-email-resend/issues/2