You have built a React or Next.js app and now you need a contact form. The traditional approach involves creating an API route, configuring an email service like SendGrid or Nodemailer, handling validation on the server, and managing error states. That is a lot of work for a simple "send me a message" form.
With Rowen, you skip all of that. You point your form at a Rowen endpoint, and submissions are stored in your dashboard, emailed to you, and optionally synced to Google Sheets. This tutorial walks you through the complete setup.
Prerequisites
- A React or Next.js project (works with both Pages Router and App Router)
- A free Rowen account — sign up at rowen.in/signup
Step 1 — Get Your Rowen Endpoint
Log into your Rowen dashboard and click "+ New Form". Copy the endpoint URL:
https://rowen.in/api/f/YOUR_FORM_ID
Step 2 — Build the Form Component
Here is a complete, production-ready contact form component with loading state, error handling, and success feedback:
import { useState } from "react";
export default function ContactForm() {
const [status, setStatus] = useState("idle"); // idle | loading | success | error
async function handleSubmit(e) {
e.preventDefault();
setStatus("loading");
const formData = new FormData(e.target);
try {
const res = await fetch("https://rowen.in/api/f/YOUR_FORM_ID", {
method: "POST",
body: formData,
});
if (res.ok) {
setStatus("success");
e.target.reset();
} else {
setStatus("error");
}
} catch (err) {
setStatus("error");
}
}
return (
<form onSubmit={handleSubmit}>
<input type="text" name="name" placeholder="Your name" required />
<input type="email" name="email" placeholder="Your email" required />
<textarea name="message" placeholder="Your message" rows={5} required />
{/* Honeypot spam protection */}
<input type="text" name="_gotcha" style={{ display: "none" }} />
<button type="submit" disabled={status === "loading"}>
{status === "loading" ? "Sending..." : "Send Message"}
</button>
{status === "success" && <p>Message sent successfully!</p>}
{status === "error" && <p>Something went wrong. Please try again.</p>}
</form>
);
}
Step 3 — Use It in Your Page
Import the component wherever you need it:
import ContactForm from "@/components/ContactForm";
export default function ContactPage() {
return (
<main>
<h1>Get in Touch</h1>
<ContactForm />
</main>
);
}
Next.js App Router: Using Server Actions?
If you are using Next.js App Router and prefer server actions, you can still use Rowen. However, the simplest approach is to keep the form as a client component (add "use client" at the top) and use the fetch pattern shown above. There is no advantage to routing through a server action when Rowen's endpoint already handles everything server-side.
TypeScript Version
If your project uses TypeScript, here is the typed version of the submit handler:
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
setStatus("loading");
const formData = new FormData(e.currentTarget);
try {
const res = await fetch("https://rowen.in/api/f/YOUR_FORM_ID", {
method: "POST",
body: formData,
});
setStatus(res.ok ? "success" : "error");
if (res.ok) e.currentTarget.reset();
} catch {
setStatus("error");
}
}
Styling the Form
Rowen does not impose any styling. The form is just a standard HTML form, so you can style it with Tailwind, CSS Modules, styled-components, or plain CSS. Here is a quick Tailwind example:
<input
type="text"
name="name"
className="w-full rounded border border-gray-300 px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Your name"
required
/>
What You Get Without Writing Backend Code
- All submissions stored in your Rowen dashboard
- Email notifications on every submission
- Spam filtering via honeypot (no CAPTCHA needed)
- Google Sheets sync (free)
- Webhook forwarding to Slack, Discord, or any URL
Wrap Up
A contact form in React or Next.js does not need an API route, a database, or an email service. With Rowen, you write a single client component, point it at your endpoint, and you are done. Create your free account at rowen.in/signup and have your form running in under two minutes.