Collect form submissions in Next.js App Router
Use a hosted endpoint to collect Next.js App Router form submissions — no API route, no database, exact domain allow-listing.
To collect form submissions in a Next.js App Router app without building your own API route, post the email straight to a hosted endpoint from a small client component. There is no database to run and no server route to secure — the endpoint accepts the submission, rejects junk, and stores it against your campaign.
Why skip an API route
A Route Handler would just forward the email and re-implement spam checks you would rather not own. Posting directly to a domain-locked endpoint keeps the browser free of secrets and leaves you nothing to maintain. Use your public campaign id (pub_…) only; never place an ss_live_ key in client code.
'use client';// app/waitlist/signup-form.tsximport { useState } from 'react';
const ENDPOINT = 'https://simple-signups.com/api/subscribe';
export function SignupForm() { const [state, setState] = useState<'idle' | 'sending' | 'done' | 'error'>('idle'); async function onSubmit(e: React.FormEvent<HTMLFormElement>) { e.preventDefault(); const form = e.currentTarget; if ((form.elements.namedItem('hp') as HTMLInputElement)?.value) return; // honeypot setState('sending'); const res = await fetch(ENDPOINT, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ campaignId: 'pub_your_campaign_id', email: (form.elements.namedItem('email') as HTMLInputElement).value, }), }); setState(res.ok ? 'done' : 'error'); } return ( <form onSubmit={onSubmit}> <input type="email" name="email" required placeholder="you@example.com" /> <input type="text" name="hp" tabIndex={-1} autoComplete="off" aria-hidden style={{ position: 'absolute', left: -9999 }} /> <button disabled={state === 'sending'}>Join waitlist</button> </form> );}Server Action alternative
If you prefer a Server Action, call the same endpoint from the server with fetch and forward the email — but for a static or lightly dynamic marketing page, the client form above is the smaller surface. Either way, allow-list every hostname you embed from (apex and www separately). See the quickstart for fields and error codes.
Preview deployments and common mistakes
If you test on Vercel preview URLs, allow-list those hostnames too — preview, apex, and www are all distinct. The common mistake is proxying the form through /api or a Route Handler for no real gain: you add another moving part, another failure mode, and another place secrets can leak, while the hosted endpoint already handles the anti-abuse work.
Related
- Receive form submissions in Astro — the same pattern for another framework.
- Contact forms use case.