Guide · Next.js App Router
Bot protection for Next.js
in five minutes.
Protect any form — signups, comments, waitlists — with invisible behavioral verification. No CAPTCHA widget, no friction for real users, and your secret key never leaves the server. This is the exact pattern humaverify.com uses on its own signup.
01Install the SDK
npm install usehumaGrab a free API key at humaverify.com/signup and set it as an environment variable: HUMA_API_KEY=huma_live_...
02Collect signals on the client
Drop in a tiny client component. It loads the collector and keeps a hidden field filled with anonymous behavioral statistics — typing rhythm, cursor physics, scroll patterns. Never the contents of what anyone types.
// components/huma-signals.tsx
"use client";
import { useEffect } from "react";
/** Collects behavioral signals and keeps a hidden field fresh. */
export function HumaSignals() {
useEffect(() => {
const s = document.createElement("script");
s.src = "https://humaverify.com/huma.js";
s.async = true;
document.head.appendChild(s);
const fill = () => {
const el = document.getElementById("humaSignals") as HTMLInputElement | null;
const Huma = (window as unknown as { Huma?: { debug: () => { features: object } } }).Huma;
if (el && Huma?.debug) el.value = JSON.stringify(Huma.debug().features);
};
const t = setInterval(fill, 1000);
document.addEventListener("submit", fill, true);
return () => {
clearInterval(t);
document.removeEventListener("submit", fill, true);
};
}, []);
return <input type="hidden" name="humaSignals" id="humaSignals" />;
}03Add it to your form
// app/signup/page.tsx
import { signUp } from "./actions";
import { HumaSignals } from "@/components/huma-signals";
export default function SignupPage() {
return (
<form action={signUp}>
<input name="email" type="email" required />
<input name="password" type="password" required />
<HumaSignals /> {/* ← the hidden signals field */}
<button type="submit">Create account</button>
</form>
);
}04Verify in the Server Action
humaFormCheck reads the hidden field, calls the verify API server-side and gives you a decision:
// app/signup/actions.ts
"use server";
import { redirect } from "next/navigation";
import { humaFormCheck } from "usehuma/next";
export async function signUp(formData: FormData) {
// Uses the HUMA_API_KEY environment variable automatically.
const check = await humaFormCheck(formData);
if (!check.human) {
// missing_signals → scripted direct POST
// below_threshold → behavior scored as bot-like
redirect("/signup?error=bot_detected");
}
// ✅ verified human — create the account
}Why this beats a naive server-side check: a scripted bot that POSTs your action directly never runs the collector, so the field is empty — rejected outright. A bot that fakes the request still has to fake an entire session of believable human micro-behavior. And if the verify API is ever unreachable,
humaFormCheck fails open so real users are never locked out.05Optional: protect API routes
// app/api/comments/route.ts — protect any Route Handler too
import { withHumaProtection } from "usehuma/next";
export const POST = withHumaProtection(async (req, huma) => {
const body = await req.json();
// huma.confidence available if you want to log it
return Response.json({ ok: true });
});