agentleFS
bkjain655bkjain655/agent-skills/skills/cloudflare-turnstile-nextjs/SKILL.md

cloudflare-turnstile-nextjs

Skill0 starsChanged 3 months agoMITView on GitHub

Add Cloudflare Turnstile (privacy-friendly CAPTCHA) to a Next.js App Router form: client widget with explicit render and a React double-render guard, server-side token verification via siteverify, and a graceful fallback so the form keeps working until keys are configured. Use when adding bot protection, CAPTCHA, human verification, or anti-spam to a form or API route.

  • Reads credentials
  • Sends data out

The file

We split this skill into the rules it gives an agent

3 rules. About 0 are common, 2 are distinctive, and 1 is very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈2

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Design principle: graceful fallback: When the keys are absent the widget is not rendered and the server skips verification, so the form …

    line 23
  2. Very specific:

    Use explicit rendering (not the implicit class="cf-turnstile" scan) — it is far more reliable in React.

    line 30
  3. Distinctive:

    Verify: widget appears; submitting without solving is blocked.

    line 107

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Cloudflare Turnstile in Next.js

Stops automated form submissions. Free, no user puzzle in most cases, and pairs well with a form that already has validation + honeypot + rate limiting.

Env vars

NEXT_PUBLIC_TURNSTILE_SITE_KEY=   # public, safe in the client bundle
TURNSTILE_SECRET_KEY=             # server-only, never NEXT_PUBLIC

Design principle: graceful fallback. When the keys are absent the widget is not rendered and the server skips verification, so the form behaves exactly as before. It only enforces once both keys exist. This lets you ship the code first and turn it on later. (NEXT_PUBLIC_* bakes in at build → redeploy after setting.)

Client (inside a "use client" form component)

Use explicit rendering (not the implicit class="cf-turnstile" scan) — it is far more reliable in React. Guard against React's double-mount.

import Script from "next/script";
import { useEffect, useRef, useState } from "react";

const SITE_KEY = process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY;

interface TurnstileApi {
  render: (el: HTMLElement, o: { sitekey: string; theme?: "light" | "dark" | "auto";
    callback?: (t: string) => void; "error-callback"?: () => void; "expired-callback"?: () => void;
  }) => string;
  reset: (id?: string) => void;
  remove: (id?: string) => void;
}
declare global { interface Window { turnstile?: TurnstileApi } }

// inside the component:
const [scriptReady, setScriptReady] = useState(false);
const [token, setToken] = useState<string | null>(null);
const boxRef = useRef<HTMLDivElement>(null);
const idRef = useRef<string | null>(null);

useEffect(() => {
  if (!SITE_KEY || !scriptReady || !window.turnstile) return;
  if (!boxRef.current || idRef.current) return;          // guard: render once
  idRef.current = window.turnstile.render(boxRef.current, {
    sitekey: SITE_KEY, theme: "dark",
    callback: setToken,
    "expired-callback": () => setToken(null),
    "error-callback": () => setToken(null),
  });
  return () => { if (idRef.current) { window.turnstile?.remove(idRef.current); idRef.current = null; } };
}, [scriptReady]);

// on submit: block if (SITE_KEY && !token); include token in the payload;
// after the response, window.turnstile?.reset(idRef.current) + setToken(null) — tokens are single-use.

// in JSX (only when SITE_KEY is set):
// <Script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit"
//   strategy="afterInteractive" onLoad={() => setScriptReady(true)} />
// <div ref={boxRef} />

Server (verify before doing any work)

async function verifyTurnstile(token: string | undefined, ip: string): Promise<boolean> {
  const secret = process.env.TURNSTILE_SECRET_KEY;
  if (!secret) return true;      // not configured → skip (graceful)
  if (!token) return false;
  try {
    const res = await fetch("https://challenges.cloudflare.com/turnstile/v0/siteverify", {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: new URLSearchParams({ secret, response: token, remoteip: ip }),
    });
    const data = (await res.json()) as { success?: boolean };
    return data.success === true;
  } catch { return false; }
}

// in the route, AFTER parsing/honeypot and BEFORE sending anything:
// if (!(await verifyTurnstile(body.turnstileToken, ip)))
//   return NextResponse.json({ error: "Verification failed." }, { status: 403 });

Accept turnstileToken: z.string().max(4096).optional() in your request schema.

Cloudflare + Vercel setup

  1. Cloudflare dashboard → Turnstile → Add widget → name it, add your production hostname (e.g. example.com; localhost is allowed automatically), mode Managed. You get a Site Key (public) + Secret Key (private).
  2. Vercel → project env: NEXT_PUBLIC_TURNSTILE_SITE_KEY (site key) and TURNSTILE_SECRET_KEY (secret key). Redeploy (site key is build-time).
  3. Verify: widget appears; submitting without solving is blocked; server 403s an invalid token.

Gotchas

  • Implicit render (class="cf-turnstile") is flaky in React SPAs — use explicit.
  • React 18 strict mode double-mounts effects — the idRef guard prevents two widgets.
  • Turnstile tokens are single-use — reset() after every submit attempt.
  • TURNSTILE_SECRET_KEY must be server-only; never prefix it NEXT_PUBLIC_.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.