← All guides

Add form notifications to a Next.js site

This guide shows you how to add a contact form to a Next.js App Router project that sends submissions to your email via Form Alert.

Prerequisites

Create the form component

Since the form uses browser APIs, it needs to be a Client Component. Create components/contact-form.tsx.

components/contact-form.tsx
"use client";

import { useState } from "react";

export default function ContactForm() {
  const [status, setStatus] = useState("idle");

  async function handleSubmit(
    e: React.FormEvent<HTMLFormElement>
  ) {
    e.preventDefault();
    setStatus("sending");

    const formData = new FormData(e.currentTarget);
    const data = Object.fromEntries(formData);

    try {
      const res = await fetch(
        "https://api.form-alert.com/v1/submit",
        {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify(data),
        }
      );

      if (res.ok) {
        setStatus("sent");
        e.currentTarget.reset();
      } else {
        setStatus("error");
      }
    } catch {
      setStatus("error");
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="hidden"
        name="access_key"
        value="YOUR_ACCESS_KEY"
      />

      <label htmlFor="name">Name</label>
      <input type="text" name="name" id="name" required />

      <label htmlFor="email">Email</label>
      <input type="email" name="email" id="email" required />

      <label htmlFor="message">Message</label>
      <textarea name="message" id="message" required />

      <button
        type="submit"
        disabled={status === "sending"}
      >
        {status === "sending"
          ? "Sending..."
          : "Send message"}
      </button>

      {status === "sent" && <p>Message sent!</p>}
      {status === "error" && (
        <p>Something went wrong. Try again.</p>
      )}
    </form>
  );
}

Add to a page

Import the component in any page. The page itself stays a Server Component — only the form is client-side.

app/contact/page.tsx
import ContactForm from "@/components/contact-form";

export default function ContactPage() {
  return (
    <main>
      <h1>Contact us</h1>
      <ContactForm />
    </main>
  );
}

Keep your access key server-side (optional)

If you don't want to expose your access key in client-side code, you can proxy the request through a Next.js Route Handler.

app/api/contact/route.ts
// app/api/contact/route.ts
import { NextResponse } from "next/server";

export async function POST(request: Request) {
  const body = await request.json();

  const res = await fetch(
    "https://api.form-alert.com/v1/submit",
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        ...body,
        access_key: process.env.FORM_ALERT_KEY,
      }),
    }
  );

  if (!res.ok) {
    return NextResponse.json(
      { error: "Failed to send" },
      { status: 500 }
    );
  }

  return NextResponse.json({ ok: true });
}

Then update the form component to POST to /api/contact instead of the Form Alert API directly.

Next steps