Next.js Quick Start
Integrate RizzForms with a Next.js (App Router) project. Choose between a simple HTML form or a server-side API route depending on your needs.
Overview
RizzForms works with any Next.js deployment: Vercel, self-hosted, or static export. The HTML form approach requires zero server-side code. The API route approach gives you more control over validation and error handling.
A: HTML Form (Simplest)
Drop a standard HTML form into any React component. This works with static export and requires no server-side code. HTML form POST is cross-origin safe by default.
// app/contact/page.tsx
export default function ContactPage() {
return (
<form
action={`https://forms.rizzness.com/f/${process.env.NEXT_PUBLIC_RIZZFORMS_TOKEN}`}
method="POST"
>
{/* Honeypot: hidden from users, catches bots */}
<input
type="text"
name="_hp"
style={{ display: "none" }}
tabIndex={-1}
autoComplete="off"
/>
<label htmlFor="name">Name</label>
<input type="text" id="name" name="name" required />
<label htmlFor="email">Email</label>
<input type="email" id="email" name="email" required />
<label htmlFor="message">Message</label>
<textarea id="message" name="message" />
<button type="submit">Send</button>
</form>
);
}
B: Server Action / API Route
For more control, create an API route that submits to RizzForms server-side. This keeps your endpoint token out of client-side code and lets you add custom validation before submission.
// app/api/contact/route.ts
import { NextResponse } from "next/server";
export async function POST(request: Request) {
const body = await request.json();
const response = await fetch(
`https://forms.rizzness.com/json/${process.env.RIZZFORMS_TOKEN}`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: body.name,
email: body.email,
message: body.message,
_hp: "", // honeypot must be empty
}),
}
);
const data = await response.json();
if (!response.ok) {
return NextResponse.json(
{ error: "Submission failed" },
{ status: response.status }
);
}
return NextResponse.json({ ok: true, data });
}
Then call this route from your client component:
// app/contact/ContactForm.tsx
"use client";
import { useState } from "react";
export default function ContactForm() {
const [status, setStatus] = useState<"idle" | "sending" | "sent" | "error">("idle");
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
setStatus("sending");
const form = new FormData(e.currentTarget);
const res = await fetch("/api/contact", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: form.get("name"),
email: form.get("email"),
message: form.get("message"),
}),
});
setStatus(res.ok ? "sent" : "error");
}
return (
<form onSubmit={handleSubmit}>
<label htmlFor="name">Name</label>
<input type="text" id="name" name="name" required />
<label htmlFor="email">Email</label>
<input type="email" id="email" name="email" required />
<label htmlFor="message">Message</label>
<textarea id="message" name="message" />
<button type="submit" disabled={status === "sending"}>
{status === "sending" ? "Sending..." : "Send"}
</button>
{status === "sent" && <p>Thank you! We'll be in touch.</p>}
{status === "error" && <p>Something went wrong. Please try again.</p>}
</form>
);
}
Environment Variables
Add your endpoint token to .env.local:
# For HTML form approach (exposed to browser)
NEXT_PUBLIC_RIZZFORMS_TOKEN=your_endpoint_token
# For API route approach (server-side only)
RIZZFORMS_TOKEN=your_endpoint_token
Use NEXT_PUBLIC_ prefix only if you need the token in
client-side code. The API route approach keeps the token server-side.
CORS Notes
HTML form POST (method="POST" with default encoding) works
cross-origin without any CORS configuration. This is a browser standard,
not a RizzForms feature.
JSON fetch from the browser to /json/:token may require CORS
headers. To avoid CORS issues, use the server-side API route approach
(option B above) so the fetch happens on your server, not in the browser.