smartformify
Pricing
App Router and Pages Router

Handle a Next.js Form Without Another API Route

Submit from a client component directly to SmartFormify. Keep the route handler optional and manage loading, errors, redirects, and confirmation inside the form UI.

Create EndpointView Next.js Code

Choose the request location

Client component

DIRECT

Shortest setup. The browser sends the request and updates the form state.

Route handler or Server Action

Optional when the application already needs server-side validation or processing.

App Router setup

Use a client component for the form state

The component reads the JSON returned by SmartFormify and uses it for success, errors, redirects, and thank-you content. The environment tab keeps endpoint URLs separate across deployments.

App Router client component

jsx

The client component sends the request and handles the JSON response returned by SmartFormify.

"use client";

import { useState } from "react";

const ENDPOINT_URL = process.env.NEXT_PUBLIC_SMARTFORMIFY_ENDPOINT;

export default function ContactForm() {
  const [state, setState] = useState({ status: "idle", message: "" });

  async function handleSubmit(event) {
    event.preventDefault();
    const form = event.currentTarget;
    if (!form.reportValidity()) return;

    setState({ status: "loading", message: "" });
    const data = Object.fromEntries(new FormData(form).entries());

    try {
      const response = await fetch(ENDPOINT_URL, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ data })
      });
      const result = await response.json();

      if (!response.ok || !result.success) {
        throw new Error(result.message || "Submission failed.");
      }

      if (result.data.redirect_url) {
        window.location.assign(result.data.redirect_url);
        return;
      }

      setState({
        status: "success",
        message: result.data.thank_you_content
      });
      form.reset();
    } catch (error) {
      setState({ status: "error", message: error.message });
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input type="email" name="email" required />
      <textarea name="message" required />
      <button disabled={state.status === "loading"}>
        {state.status === "loading" ? "Sending..." : "Send"}
      </button>
      {state.message && (
        <div
          role={state.status === "error" ? "alert" : "status"}
          dangerouslySetInnerHTML={{ __html: state.message }}
        />
      )}
    </form>
  );
}

STEP 01

Component

Add use client, form state, and the submit handler.

STEP 02

Environment

Set the endpoint URL for local, preview, and production builds.

STEP 03

Domains

Allow only deployments that submit directly from the browser.

Deployment checks

Test every browser-facing deployment

A preview domain needs access only when the preview form sends directly to the endpoint.

  • Add localhost while testing direct browser submissions
  • Add the current preview domain when reviewers need the live form
  • Add the production domain before launch
  • Remove preview domains that no longer need access
  • Test loading, success, error, and redirect states