Guide · Clerk + Next.js App Router

Bot-proof your Clerk sign-up.
Bots never see the form.

Clerk handles your authentication beautifully — but anything that renders a public sign-up form attracts automated registrations. This guide gates Clerk's <SignUp/> behind invisible behavioral verification: real visitors flow through after a few seconds of natural interaction; bots hit a wall they can't script around. Every snippet below was live-tested on Next.js 16 + @clerk/nextjs 7 + usehuma 1.2.1.

01Install

npm install @clerk/nextjs usehuma

You need usehuma@1.2.1+ — earlier versions verified before signals were collected. Grab keys from your Clerk dashboard and a free useHUMA key at humaverify.com/signup.

# .env.local — use YOUR keys from dashboard.clerk.com
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_••••••••
CLERK_SECRET_KEY=sk_test_••••••••
NEXT_PUBLIC_HUMA_KEY=huma_live_••••••••

02Clerk middleware (Next.js 16: proxy.ts)

Next.js 16 renamed middleware.ts to proxy.ts — same idea, earlier in the pipeline. Create it at the project root:

// proxy.ts  (Next.js 16 — replaces middleware.ts)
import { clerkMiddleware } from "@clerk/nextjs/server";

export default clerkMiddleware();

export const config = {
  matcher: [
    "/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)",
    "/(api|trpc)(.*)",
  ],
};

03Wrap your app with ClerkProvider

// app/layout.tsx
import { ClerkProvider } from "@clerk/nextjs";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <ClerkProvider>
      <html lang="en">
        <body>{children}</body>
      </html>
    </ClerkProvider>
  );
}

04Gate the sign-up page

// app/sign-up/[[...sign-up]]/page.tsx
"use client";

import { SignUp } from "@clerk/nextjs";
import { HumaGate } from "usehuma/react";

export default function SignUpPage() {
  return (
    <div style={{ display: "flex", justifyContent: "center", padding: 48 }}>
      <HumaGate
        apiKey={process.env.NEXT_PUBLIC_HUMA_KEY!}
        userId="clerk_signup_visitor"
        fallback={<p>Verifying you&apos;re human…</p>}
      >
        <SignUp />
      </HumaGate>
    </div>
  );
}
How it behaves: the page shows your fallback for ~4 seconds while useHUMA collects behavioral signals (cursor physics, typing rhythm, scroll). A real human passes invisibly and the Clerk form appears. A bot — which can't fake a believable session — gets a "couldn't verify" retry prompt instead of the form. The form is never even in the DOM for unverified sessions; check the page source.

Why gate instead of CAPTCHA?

A CAPTCHA punishes every legitimate user to inconvenience bots that increasingly solve puzzles anyway. The gate inverts the burden: humans do nothing, and automation has to imitate an entire session of human micro-behavior — across whatever framework it drives the browser with.

Clerk for auth, useHUMA for humanity.

14-day free trial · one API key · zero PII.

Get your API key →