Add a working form to a Next.js 14 app in 10 minutes
A real Next.js 14 App Router form: a client component, a Server Action to a hosted backend, success/error states, and spam protection — no API route.
Next.js makes the UI easy and the backend of a form annoying: you either stand up an API route, wire an email provider, and handle spam yourself, or you reach for a hosted form backend and skip all of it. This is the second path, done properly with the App Router — Server Action included.
You’ll have a working contact form, emailing you on every submission, with no server code of your own to maintain.
Step 1 — Create the form endpoint
Sign up, create a form, set your fields (name, email, message), and copy the endpoint:
https://login.ollastack.com/api/submit/<your-slug>
That URL accepts JSON and form-encoded bodies and returns 201 with { success: true, id }. That’s your entire backend.
Step 2 — The client component
"use client";
import { useState } from "react";
import { submitContact } from "./actions";
export function ContactForm() {
const [state, setState] = useState<"idle" | "sending" | "sent" | "error">("idle");
return (
<form
onSubmit={async (e) => {
e.preventDefault();
setState("sending");
const data = new FormData(e.currentTarget);
const ok = await submitContact(data);
setState(ok ? "sent" : "error");
}}
>
{state === "sent" ? (
<p>Thanks — we'll be in touch.</p>
) : (
<>
<input name="name" placeholder="Name" required />
<input name="email" type="email" placeholder="you@example.com" required />
<textarea name="message" placeholder="Message" required />
{/* Honeypot: real users never see it; bots fill it. */}
<input name="_gotcha" tabIndex={-1} autoComplete="off" style={{ display: "none" }} />
<button disabled={state === "sending"}>
{state === "sending" ? "Sending…" : "Send"}
</button>
{state === "error" && <p>Something went wrong — try again.</p>}
</>
)}
</form>
);
}
Step 3 — The Server Action
Keeping the POST on the server means the endpoint isn’t called straight from the browser, and you can add server-only logic later (logging, redirects) without touching the component.
// app/contact/actions.ts
"use server";
export async function submitContact(form: FormData): Promise<boolean> {
const res = await fetch(
"https://login.ollastack.com/api/submit/your-slug",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: form.get("name"),
email: form.get("email"),
message: form.get("message"),
_gotcha: form.get("_gotcha"), // honeypot passes through
}),
}
);
return res.ok;
}
That’s the whole integration. No API route, no Nodemailer, no spam library.
Step 4 — Notifications
In the form’s settings, set the email that should receive submissions (verify it first — a form can only notify a verified address, which prevents your form from being pointed at a stranger’s inbox). Want replies to go straight to the person who wrote in? The submission’s email field is automatically used as the notification’s Reply-To, so hitting “Reply” answers the lead.
Step 5 — Spam, without writing any
You already added the honeypot. Beyond that, submissions run through a layered pipeline (IP blocklist, keyword/regex, link limits, Akismet, and an ML classifier) automatically. The important guarantee: a real lead is never silently dropped — anything the ML model alone is unsure about is delivered and labeled [Possible spam], not deleted, and you can recover it in one click.
Optional — render it server-side or skip the UI entirely
Two shortcuts worth knowing:
- No UI at all: every form gets a hosted page at
login.ollastack.com/f/<slug>— link to it and you’re done. - Progressive enhancement: because the endpoint also accepts a plain
multipart/form-dataPOST, the same form works withaction="https://login.ollastack.com/api/submit/your-slug" method="POST"even before JavaScript loads. The Server Action above just gives you inline success/error states.
Where to go next
- Forward submissions to Slack/Discord or your own webhook (signed, with retries + replay).
- Let an AI agent submit the same form with a scoped Bearer token (it discovers the API from
/api/openapi.json). - Add file uploads (the endpoint handles
multipart/form-data).
Create your form — free tier is 100 submissions/month, no credit card. The endpoint is the only thing you have to remember.
Frequently asked questions
How do I add a form to Next.js 14?
Use a client component with a Server Action that POSTs to a hosted form endpoint. You get email, spam filtering and webhooks with no API route of your own.
Do I need an API route?
No — a Server Action or a direct fetch posts to the hosted endpoint; there's no email or spam plumbing to build.
Does it handle spam?
Yes — a layered spam pipeline runs server-side, with an optional honeypot field.
Last updated June 19, 2026. Spotted something out of date? Email hello@ollastack.com.