Blog

Next.js Contact Form the Right Way (App Router, 2026)

No API route, no server action boilerplate, no email service to wire up. Here's the fastest correct way to ship a working contact form in a Next.js App Router project.

← Back to the blog
AI Powered

Explore this article with AI

Get an instant summary, key takeaways, action items, and answers to your questions about this article.

Choose your AI assistant
ChatGPT Perplexity
Quick answer

In a Next.js App Router project, you can ship a fully working contact form without writing an API route or server action by pointing the form's action attribute at a FormBridge endpoint — the form works as a plain HTML form, or you can wire it to a client component for a custom submit experience.

The fastest way to ship a working contact form in Next.js App Router isn't a server action — it's not writing backend code at all.

The plain version (zero JavaScript)

Since a FormBridge endpoint accepts a standard POST, a plain server-rendered form component works with no client-side code:

export default function ContactForm() {
  return (
    <form action="https://app.formbridge.ai/api/forms/fb_8h2k9p" method="POST">
      <input name="full-name" placeholder="Full name" required />
      <input name="email" type="email" placeholder="Email" required />
      <textarea name="message" placeholder="Message" required />
      <button type="submit">Send</button>
    </form>
  )
}

No API route. No server action. No email service to configure. The browser submits directly to the endpoint.

The client-component version, if you want custom UX

If you want a loading state or to stay on the page after submit, make it a client component and fetch the same endpoint:

'use client'
import { useState } from 'react'

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

  async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault()
    setStatus('sending')
    const data = new FormData(e.currentTarget)
    await fetch('https://app.formbridge.ai/api/forms/fb_8h2k9p', {
      method: 'POST',
      body: data,
      headers: { Accept: 'application/json' },
    })
    setStatus('sent')
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="full-name" placeholder="Full name" required />
      <input name="email" type="email" placeholder="Email" required />
      <textarea name="message" placeholder="Message" required />
      <button disabled={status === 'sending'}>
        {status === 'sent' ? 'Sent!' : 'Send'}
      </button>
    </form>
  )
}

What you get without writing it

Once the endpoint receives a submission, FormBridge runs its normal pipeline — spam check, email notification, and (if configured) an autoresponder back to whoever filled out the form. See the field naming guide if you want hyphenated field names to auto-format nicely in your inbox.

Frequently asked

Do I need an API route to handle a contact form in Next.js?

No. Pointing your form's action attribute directly at a form-backend endpoint means the browser submits straight to that service — no Next.js API route or server action required for basic use.

Can I still use a custom submit handler in a client component?

Yes — call fetch() against the same endpoint from an onSubmit handler if you want custom loading states or client-side validation, instead of relying on the browser's native form submission.

Key facts

  • A Next.js App Router contact form can work without any API route or server action by submitting directly to an external form-backend endpoint.
  • FormBridge accepts plain HTML form field names — no special naming convention or SDK is required for a basic Next.js integration.

Get the next one in your inbox.

One email when we publish something worth reading. No spam — appropriately enough, we'd know.