Next.js contact form, no API route
Next.js is a full-stack framework, so you could build a Route Handler and wire up an email SDK. For a contact or lead form that’s more surface area than the job needs. The App Router gives you a cleaner option, a Server Action that forwards to an endpoint, or a plain client-side fetch. Both skip the /api route entirely.
Next.js can render a contact form, but the App Router gives you no place to email submissions by default. Two options work: a client fetch, or a Server Action that forwards the fields to a MakeTheForm endpoint. Either way the endpoint filters spam, emails you, and stores each submission: no /api route to build.
The minimal integration
This is a Server Action in the App Router: no /api route, no database. It runs on the server and forwards to your endpoint; pair it with useActionState for pending state. A client-side fetch works too if your form is a client component.
// app/contact/actions.ts
'use server';
export async function submitContact(prevState, formData) {
const response = await fetch('https://mtform.co/f/your-form-key', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(Object.fromEntries(formData)),
});
const result = await response.json();
if (!response.ok) {
return { ok: false, message: result.error?.message ?? 'Unable to send your message.' };
}
return { ok: true, message: 'Thanks. We got your message.' };
}
// app/contact/page.tsx
'use client';
import { useActionState } from 'react';
import { submitContact } from './actions';
export default function ContactPage() {
const [state, action, pending] = useActionState(submitContact, null);
return (
<form action={action}>
<input name="name" placeholder="Name" required />
<input type="email" name="email" placeholder="Email" required />
<textarea name="message" placeholder="Message" required />
<input name="_mtf_honeypot" tabIndex={-1} autoComplete="off" aria-hidden="true"
style={{ position: 'absolute', left: '-9999px' }} />
<button type="submit" disabled={pending}>{pending ? 'Sending…' : 'Send'}</button>
{state && <p role={state.ok ? 'status' : 'alert'}>{state.message}</p>}
</form>
);
}A Server Action: no API route needed. Returns a serializable result the form renders inline.
Step by step
- 01
Create the endpoint
Create a MakeTheForm form to get your public endpoint URL and inbox.
- 02
Add a Server Action
In a "use server" file, write one function that fetches the endpoint with the submitted FormData as JSON and returns a serializable { ok, message }.
- 03
Wire the form and pending state
Bind the action with useActionState, disable the button while pending, and render the returned message inline.
- 04
Verify and deploy
Verify your recipient email once, deploy to Vercel or your host, and submit to confirm delivery to your inbox and email.
Common pitfalls
Assuming a Server Action needs a secret key
The form endpoint isn’t a secret. It’s a public address, and it works the same from the browser. A Server Action is a fine place to call it from, but don’t assume you now need an env-var API key. There isn’t one.
Mixing "use server" into a "use client" file
You can’t declare a Server Action inside a client component. Put the action in its own "use server" module and import it, or the build fails. Keep the form component and the action in separate files.
Troubleshooting
| Symptom | What is happening | Fix |
|---|---|---|
| action not firing | The form uses onSubmit instead of the action prop, so the Server Action never runs. | Bind it with <form action={action}> from useActionState, or call the action from your handler. |
| pending never ends | The action throws instead of returning, so useActionState can’t settle. | Catch fetch errors and return { ok: false, message } rather than throwing inside the action. |
| 403 | Allowed-domains is on and your deployed domain isn’t listed. | Add your production and preview domains in the form’s spam settings, or leave it public. |
Frequently asked questions
→Can I build a Next.js contact form without a backend?
Yes. Point the form at a hosted endpoint. Either via a client fetch or a Server Action that forwards to it. The endpoint receives, spam-filters, emails, and stores each submission, so you don’t build or host a backend of your own.
→How do I use a Server Action to send a form to email?
Write a "use server" function that fetches an email-capable endpoint with the submitted FormData and returns a result. Bind it with useActionState for pending and error state. The endpoint handles the actual email delivery and spam filtering.
→Do I need an /api route for a Next.js form?
No. A Server Action or a direct client-side fetch to a form endpoint both work without a Route Handler. You only need an /api route for custom server processing the endpoint doesn’t already cover.
Keep going
Last reviewed July 23, 2026
Ship this form for real
Create an endpoint, paste it in, and watch the first submission land in your inbox, in under three minutes.
Create free endpoint