Add a form to Next.js: App Router or Pages
Post from a client component, or proxy through a route handler so the Public Key stays server-side. Works identically in the App Router and the Pages Router.
- Free plan
- No plugin to install
- Set up in minutes
Next.js in three steps.
- 1
Create a form and copy its Public Key
Sign up and copy the Public Key. If you proxy through a route handler, the Public Key goes in a server env var, not NEXT_PUBLIC_.
- 2
Add a route handler that forwards
A small POST handler reads the body, builds the form-specific URL from a server environment variable, and relays the request so the browser never sees the Public Key.
- 3
Post to your handler from the page
The form component fetches /api/contact instead of the endpoint directly. Same response, same inbox, Public Key kept server-side.
Copy this into Next.js.
Swap in your form’s key and submissions start landing in your inbox.
// app/api/contact/route.js
export async function POST(req) {
const body = await req.json();
return fetch("https://api.surveyowl.co/v1/submit/" + encodeURIComponent(process.env.FORM_PUBLIC_KEY), {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
}
// app/page.js — post to your handler, not the endpoint
await fetch("/api/contact", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, message }),
});What people build with it.
Contact form on a Next.js marketing site.
Server-rendered landing pages where the Public Key must stay server-side.
App Router projects that already use route handlers for everything else.
Next.js forms, answered.
1. Create a form and copy its Public Key: Sign up and copy the Public Key. If you proxy through a route handler, the Public Key goes in a server env var, not NEXT_PUBLIC_. 2. Add a route handler that forwards: A small POST handler reads the body, builds the form-specific URL from a server environment variable, and relays the request so the browser never sees the Public Key. 3. Post to your handler from the page: The form component fetches /api/contact instead of the endpoint directly. Same response, same inbox, Public Key kept server-side.
Next.js gives you a choice the static-site pages don’t: post straight from the browser like any static site, or add a tiny route handler that forwards to the endpoint with the Public Key read from server environment variables. The second keeps the Public Key off the client entirely.
The Pages Router version is the same shape: pages/api/contact.js exports a default handler with the identical body. In both routers the Public Key lives in process.env on the server, so it never ships in client JavaScript.