← All guides

Next.js

React Forms with Next.js

Next.js App Router client components can submit to Formward with a simple fetch call, giving you full control over loading states and success feedback without any server-side code.

  1. 01Create your form in Formward

    Sign up at formward.eu, create a new form, and copy the endpoint URL (https://formward.eu/f/your-form-id).

  2. 02Create a ContactForm client component

    Add the file app/contact/ContactForm.tsx (or .jsx) with the 'use client' directive. The component uses controlled inputs and fetch to POST to Formward with Accept: application/json, avoiding a full-page redirect.

    // app/contact/ContactForm.tsx
    'use client';
    
    import { useState } from 'react';
    
    interface Fields {
      name: string;
      email: string;
      message: string;
    }
    
    export default function ContactForm() {
      const [fields, setFields] = useState<Fields>({ name: '', email: '', message: '' });
      const [status, setStatus] = useState<'idle' | 'submitting' | 'success' | 'error'>('idle');
    
      function handleChange(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) {
        setFields({ ...fields, [e.target.name]: e.target.value });
      }
    
      async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
        e.preventDefault();
        setStatus('submitting');
    
        try {
          const res = await fetch('https://formward.eu/f/your-form-id', {
            method: 'POST',
            headers: {
              'Content-Type': 'application/x-www-form-urlencoded',
              Accept: 'application/json',
            },
            body: new URLSearchParams({ ...fields, _gotcha: '' }).toString(),
          });
    
          if (!res.ok) throw new Error('Network error');
          setStatus('success');
          setFields({ name: '', email: '', message: '' });
        } catch {
          setStatus('error');
        }
      }
    
      if (status === 'success') {
        return <p className="text-green-700">Thank you, your message has been sent!</p>;
      }
    
      return (
        <form onSubmit={handleSubmit} className="flex flex-col gap-4 max-w-md">
          <label className="flex flex-col gap-1 text-sm font-medium">
            Name
            <input type="text" name="name" value={fields.name} onChange={handleChange} required
              className="rounded border border-gray-300 px-3 py-2 text-sm" />
          </label>
          <label className="flex flex-col gap-1 text-sm font-medium">
            Email
            <input type="email" name="email" value={fields.email} onChange={handleChange} required
              className="rounded border border-gray-300 px-3 py-2 text-sm" />
          </label>
          <label className="flex flex-col gap-1 text-sm font-medium">
            Message
            <textarea name="message" value={fields.message} onChange={handleChange} rows={5} required
              className="rounded border border-gray-300 px-3 py-2 text-sm resize-y" />
          </label>
          {/* honeypot: keep hidden */}
          <input type="text" name="_gotcha" style={{ display: 'none' }} tabIndex={-1} autoComplete="off" />
          <button type="submit" disabled={status === 'submitting'}
            className="self-start rounded bg-black px-5 py-2 text-sm text-white disabled:opacity-50">
            {status === 'submitting' ? 'Sending…' : 'Send'}
          </button>
          {status === 'error' && (
            <p className="text-red-600 text-sm">Something went wrong. Please try again.</p>
          )}
        </form>
      );
    }
  3. 03Render on a page

    Create app/contact/page.tsx as a server component that imports and renders ContactForm. Because the form component carries 'use client', Next.js automatically bundles it for the browser while keeping the page shell server-rendered.

    // app/contact/page.tsx
    import ContactForm from './ContactForm';
    
    export const metadata = { title: 'Contact' };
    
    export default function ContactPage() {
      return (
        <main className="mx-auto max-w-2xl px-4 py-16">
          <h1 className="text-3xl font-bold mb-8">Get in touch</h1>
          <ContactForm />
        </main>
      );
    }
  4. 04Test and view submissions

    Run next dev, open http://localhost:3000/contact, and submit the form. The component shows a success message without navigating away. Open the Formward dashboard to confirm the submission arrived.

Ready to collect your first submission?

Create your form
React Forms with Next.js | Formward