splitforms.com
All articles/ TUTORIALS9 MIN READPublished June 21, 2026

How to Add a Form Backend to a v0 (Vercel) App (2026)

v0 generates a beautiful form but no endpoint to receive it. Add a working form backend in 2 minutes — one POST URL, email + webhook delivery, zero server code.

✶ Written by
splitforms.com / blog

Founder of splitforms — the form backend API for developers. Writes about form UX, anti-spam, and shipping web apps without backend code.

Why a v0 form has no backend

v0 by Vercel is a generative UI tool. You prompt it, and it returns polished React components — usually with Tailwind and shadcn/ui — that you can drop into a Next.js app. It is excellent at the interface. What it deliberately does not do is stand up server infrastructure. A v0 "contact form" is a client component: inputs, validation, a styled submit button, and an onSubmit that, out of the box, doesn't send anywhere real.

That's by design — v0 generates UI for developers who already have (or will build) the backend. The gap shows up the moment you ship: the form looks production-ready, someone fills it out, and the submission evaporates. There was never a server route to catch it.

You have two ways to close that gap. Write the backend yourself with a Next.js Route Handler or Server Action plus an email provider — appropriate when the form drives your app's own data. Or, for the common case of a contact / waitlist / lead form whose job is "capture it and tell me," point the form at a hosted backend and skip the server code entirely. This guide does the second; if you want the first, the trade-offs are in Server Actions vs form backends.

Step 1: Get a free splitforms access key (1 minute)

splitforms is the backend we'll point the v0 form at. Free tier: 500 submissions/month with dashboard storage and AI spam filtering; Starter adds email forwarding and webhooks — no credit card.

  1. Go to splitforms.com/login, enter your email, paste the 6-digit code
  2. Copy the access key (sf_live_...)
  3. Set your notification email under Settings → Notifications

The key is a public identifier — safe in client code. You can hardcode it or, if you prefer, expose it as a NEXT_PUBLIC_SPLITFORMS_KEY environment variable in Vercel. Both are fine; it's not a secret.

Step 2: Point the v0 form's submit at splitforms

Keep every component v0 generated. Change only the submit handler. Here it is for a typical v0 + shadcn/ui form — drop it into the existing onSubmit:

"use client";

import { useState } from "react";

const ACCESS_KEY = process.env.NEXT_PUBLIC_SPLITFORMS_KEY ?? "YOUR_ACCESS_KEY";

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

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

    const data = new FormData(e.currentTarget);
    data.append("access_key", ACCESS_KEY);

    try {
      const res = await fetch("https://splitforms.com/api/submit", {
        method: "POST",
        body: data,
      });
      if (!res.ok) throw new Error();
      setStatus("ok");
      e.currentTarget.reset();
    } catch {
      setStatus("error");
    }
  }

  return (
    <form onSubmit={onSubmit}>
      {/* keep v0's generated shadcn/ui inputs and styling as-is */}
      <input name="name" required />
      <input name="email" type="email" required />
      <textarea name="message" required />

      {/* honeypot */}
      <input name="botcheck" type="checkbox" tabIndex={-1}
             autoComplete="off" style={{ display: "none" }} />

      <button type="submit" disabled={status === "sending"}>
        {status === "sending" ? "Sending..." : "Submit"}
      </button>

      {status === "ok" && <p>Thanks — we got your message.</p>}
      {status === "error" && <p>Something went wrong. Try again.</p>}
    </form>
  );
}

That's the entire backend. No app/api/contact/route.ts, no email SDK, no environment plumbing beyond the optional public key. The three things that matter: append access_key to the FormData, keep a name on every input (these become the fields in your dashboard and Starter inbox emails), and keep the hidden botcheck honeypot.

If your v0 form uses react-hook-form (common with shadcn/ui), submit the validated values the same way — build a FormData or post JSON; splitforms accepts both multipart/form-data and application/x-www-form-urlencoded. The pattern is identical to sending form data with fetch.

Why this beats writing a Route Handler for a contact form

You can write app/api/contact/route.ts, validate the body, call Resend or Nodemailer, handle errors, and add spam protection. For a form that's genuinely part of your product, do that. For a contact or lead form, here's what the hosted route saves you:

  • No email provider setup. No Resend account, API key, or domain verification — delivery is included with dedicated SMTP.
  • No spam code. Honeypot plus an AI classifier run server-side; you don't build or maintain them.
  • No cold-start or CORS debugging. The browser posts straight to a stable endpoint; nothing to deploy or warm up.
  • A dashboard for free. Search, filter, and export submissions without building an admin UI or querying a table.

The submission still lands as structured data you can pipe anywhere — including back into your own database via a webhook to a Vercel Route Handler if you want a copy. You get the convenience of the hosted backend without giving up control of the data.

Step 3: Test in preview and ship

Submit a test entry from v0's preview or local dev. Within ~5 seconds you should get a notification email and see the submission in your splitforms dashboard. Because the post goes directly to splitforms, it behaves the same in preview and in production — no "works locally, breaks on Vercel" surprises.

Before launch, open the splitforms security tab and add your Vercel production domain (and any custom domain) to Allowed Domains. If something doesn't arrive, check the dashboard first — present-but-no-email means a forwarding issue; absent means the POST didn't reach splitforms (verify the URL and access key in the console). Full checklist: contact form not working.

What to do next

FAQ

Why does my v0 form not submit anywhere?

v0 generates UI components — React, Tailwind, shadcn/ui. It produces the form's look and client behavior, but it doesn't create a server route to receive the submission. A generated v0 form typically has an onSubmit that does nothing, logs to the console, or shows a fake success state. To capture real submissions you need a backend: either a Next.js Route Handler / Server Action you write yourself, or a hosted form backend you POST to. The hosted route is a one-line change to the submit handler.

Should I use a Next.js Server Action or a form backend for a v0 form?

If the form feeds your app's own database and business logic, a Server Action or Route Handler is the right tool. If the form is a contact, waitlist, lead, or demo-request form whose job is 'notify me and store the submission,' a hosted form backend is less code and gives you email, a dashboard, spam filtering, and webhooks for free. Many teams use both: Server Actions for in-app data, a form backend for the marketing-style forms. The detailed trade-off is in our Server Actions vs form backend comparison.

Does this work whether or not I've deployed the v0 app to Vercel?

Yes. Because the form posts directly to the splitforms endpoint from the browser, it works in v0's preview, in local development, and in production on Vercel — no API route to deploy, no environment to configure. The same handler behaves identically everywhere, which removes the usual 'works in preview, breaks in prod' class of form bugs.

Is the access key safe in a v0 / Vercel client component?

Yes. The access key is a public identifier meant to live in client code. It is not a secret and doesn't need to be an environment variable (though you can put it in NEXT_PUBLIC_ one for tidiness). Abuse is prevented server-side via rate limiting, Allowed Domains, and AI spam classification. If a key is ever abused, rotate it in the dashboard and redeploy.

Can I keep v0's shadcn/ui form components and still use this?

Yes. Keep every generated component — Input, Textarea, Button, the whole shadcn/ui form — exactly as v0 built them. You only change the submit handler to POST to splitforms. The design system, the validation, the accessibility attributes all stay. This guide's handler drops into the existing onSubmit without touching markup.

How do I send v0 form submissions to Slack or a database too?

Configure destinations once in the splitforms dashboard. Every submission delivers to email plus any signed webhooks you add — Slack, Discord, Notion, Google Sheets, or your own Vercel Route Handler if you want a copy in your database. None of that lives in your v0 app, so adding a destination later needs no redeploy.

About the author
✻ ✻ ✻

Get your free contact form API key in 60 seconds.

500 free form submissions per month. No credit card. No SDK, no PHP, no plugin. Drop one POST endpoint in your form and submissions land in your dashboard. Starter adds inbox delivery.

Generate access key →Read the docs
founders pricing locked in · early access open