2 min read
Next.js recipe
Three ways to wire a Formspring endpoint into a Next.js project.
App Router — server action
// app/contact/page.tsx
'use server';
async function submit(formData: FormData) {
await fetch(process.env.FORMSPRING_URL!, {
method: 'POST',
body: formData,
});
}
export default function Contact() {
return (
<form action={submit}>
<input name="email" type="email" required />
<textarea name="message" required />
<button type="submit">Send</button>
</form>
);
}
Pages Router — direct POST
// pages/contact.tsx
export default function Contact() {
return (
<form action={process.env.NEXT_PUBLIC_FORMSPRING_URL} method="POST">
<input name="email" type="email" required />
<textarea name="message" required />
<button type="submit">Send</button>
</form>
);
}
Route handler that proxies + adds metadata
// app/api/contact/route.ts
export async function POST(request: Request) {
const body = await request.json();
const enriched = { ...body, _source: 'website-contact' };
const r = await fetch(process.env.FORMSPRING_URL!, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(enriched),
});
return Response.json(await r.json(), { status: r.status });
}
Verify a webhook in a route handler
// app/api/formspring-webhook/route.ts
import { createHmac, timingSafeEqual } from 'node:crypto';
export async function POST(request: Request) {
const sig = request.headers.get('x-formspring-signature') ?? '';
const raw = await request.text();
const expected = createHmac('sha256', process.env.WEBHOOK_SECRET!)
.update(raw)
.digest('hex');
if (sig.length !== expected.length || !timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
return new Response('unauthorized', { status: 401 });
}
const body = JSON.parse(raw);
// …handle the submission
return new Response('ok');
}