Back to blog
GuideNext.jsserver actionsAPI routes

Form Handling in Next.js: Server Actions vs API Routes vs Form Backend

9 min read

Three ways to handle forms in Next.js

Next.js gives you multiple ways to handle form submissions. Each has trade-offs. Let's compare them honestly.

Option 1: Server Actions

Next.js 14+ introduced server actions — functions that run on the server and can be called directly from client components.

"use server";

export async function submitContactForm(formData: FormData) {
  const name = formData.get("name") as string;
  const email = formData.get("email") as string;
  const message = formData.get("message") as string;

  // Validate
  if (!name || !email || !message) {
    return { error: "All fields are required" };
  }

  // Store in database
  await db.submission.create({
    data: { name, email, message },
  });

  // Send notification email
  await sendEmail({
    to: "you@example.com",
    subject: `New contact from ${name}`,
    body: message,
  });

  return { success: true };
}

Pros:

  • No API route needed — less boilerplate
  • Type-safe end-to-end with TypeScript
  • Progressive enhancement — works without JavaScript
  • Built into Next.js — no extra dependencies

Cons:

  • You still need to write the email sending logic
  • You still need to set up spam protection
  • You still need a database for storing submissions
  • No dashboard for viewing submissions
  • Tightly coupled to your Next.js app

Best for: Forms where submission data maps directly to your database models and you already have email infrastructure set up.

Option 2: API Routes

The traditional approach — create a POST endpoint in app/api/:

// app/api/contact/route.ts
import { NextResponse } from "next/server";

export async function POST(request: Request) {
  const body = await request.json();
  const { name, email, message } = body;

  if (!name || !email || !message) {
    return NextResponse.json(
      { error: "All fields required" },
      { status: 400 }
    );
  }

  // Store, send email, etc.
  await db.submission.create({ data: { name, email, message } });
  await sendEmail({ /* ... */ });

  return NextResponse.json({ success: true });
}

Pros:

  • Standard REST pattern — works with any frontend
  • Easy to test independently
  • Can be consumed by mobile apps, other services
  • Full control over request/response

Cons:

  • Same as server actions: you own all the infrastructure
  • More boilerplate than server actions
  • Need to handle CORS if used cross-origin

Best for: Forms that need to be consumed by multiple frontends, or when you want a REST API for other integrations.

Option 3: Form backend service (InputHaven)

Point your form directly at a third-party endpoint:

"use client";
import { useState } from "react";

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

  async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault();
    setStatus("loading");

    const res = await fetch("https://inputhaven.com/api/v1/submit", {
      method: "POST",
      body: new FormData(e.currentTarget),
      headers: { Accept: "application/json" },
    });

    setStatus(res.ok ? "success" : "error");
  }

  if (status === "success") return <p>Thank you! We'll be in touch.</p>;

  return (
    <form onSubmit={handleSubmit}>
      <input type="hidden" name="_form_id" value="your-form-id" />
      <input type="text" name="name" placeholder="Name" required />
      <input type="email" name="email" placeholder="Email" required />
      <textarea name="message" placeholder="Message" required />
      <button disabled={status === "loading"}>
        {status === "loading" ? "Sending..." : "Send"}
      </button>
    </form>
  );
}

Pros:

  • Zero backend code — no server action, no API route
  • Spam protection built in (honeypot, keywords, AI, rate limiting)
  • Email notifications configured in dashboard
  • Submissions dashboard with search and export
  • File uploads, webhooks, auto-responses included
  • Works if you move away from Next.js

Cons:

  • Third-party dependency
  • Monthly cost at scale (though free tier covers most use cases)
  • Less control over server-side processing logic

Best for: Contact forms, feedback widgets, lead capture, newsletter signups, and any form where you don't need custom server-side business logic.

Decision framework

QuestionServer ActionAPI RouteForm Backend
Need custom business logic?YesYesNo
Need email notifications?Build itBuild itIncluded
Need spam protection?Build itBuild itIncluded
Need a submissions dashboard?Build itBuild itIncluded
Need file uploads?Build itBuild itIncluded
Need webhooks?Build itBuild itIncluded
Works without Next.js?NoMaybeYes
Time to implement?Hours–daysHours–daysMinutes

The hybrid approach

You can combine approaches. Use server actions for forms tightly integrated with your app (like profile settings), and use InputHaven for standalone forms (like contact pages and feedback widgets).

This gives you the best of both worlds: custom logic where you need it, and zero maintenance where you don't.

Getting started with InputHaven + Next.js

  • Create a free InputHaven account
  • Create a form in the dashboard
  • Copy the form ID
  • Use the code example above

The entire integration is one fetch call with a form ID. No API keys, no backend code, no database setup.

Ready to try InputHaven?

500 free submissions/month. No credit card required.

Get Started Free