Contact form for Next.js

The reflex in Next.js is to write an API route that relays the form to an email service. You can skip that entire layer: post directly to FormDock from the client. It works with the App Router, the Pages Router, and — unlike an API route — with output: 'export' static builds.

app/contact/ContactForm.tsx
"use client";

import { useState } from "react";

export function ContactForm() {
  const [status, setStatus] = useState<"idle" | "sending" | "sent" | "error">("idle");

  async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault();
    setStatus("sending");
    const data = Object.fromEntries(new FormData(e.currentTarget));
    const res = await fetch(process.env.NEXT_PUBLIC_FORM_ENDPOINT!, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(data),
    });
    setStatus(res.ok ? "sent" : "error");
  }

  if (status === "sent") return <p>Thanks — we got your message.</p>;

  return (
    <form onSubmit={handleSubmit}>
      <input name="name" required placeholder="Name" />
      <input name="email" type="email" required placeholder="Email" />
      <textarea name="message" placeholder="Message" />
      <button disabled={status === "sending"}>Send</button>
    </form>
  );
}
  • Put the endpoint in NEXT_PUBLIC_FORM_ENDPOINT (e.g. https://formdock.app/f/your-key) so staging and production can use different forms.
  • No API route means nothing to deploy, no serverless cold starts, and static export keeps working.
  • Prefer zero JavaScript? A plain HTML form with action + method works in any server or client component — see the HTML guide.
  • Lock CORS to your domains in form settings before going live.

Get your endpoint: create a free FormDock account (100 submissions/month), add a form, and swap your-form-key for the real key.