# Next.js (App Router) For `app/`-directory Next.js projects. The widget needs to run on the client, so the page or component must be `"use client"`. The siteverify call lives server-side, either in a Server Action or an API route. ```tsx title="app/signup/page.tsx" "use client"; import Script from "next/script"; import { type FormEvent, useRef, useState } from "react"; type TurnstileWidgetId = string; type TurnstileApi = { render: ( container: HTMLElement, options: { sitekey: string; action: string; callback: (token: string) => void; }, ) => TurnstileWidgetId; reset: (widgetId: TurnstileWidgetId) => void; }; declare global { interface Window { turnstile: TurnstileApi; } } export default function SignupPage() { const turnstileContainer = useRef(null); const signupWidgetId = useRef(null); const [token, setToken] = useState(""); function renderTurnstile() { if (!turnstileContainer.current || signupWidgetId.current !== null) return; signupWidgetId.current = window.turnstile.render(turnstileContainer.current, { sitekey: "YOUR_SITEKEY", action: "signup", callback: setToken, }); } async function handleSubmit(e: FormEvent) { e.preventDefault(); try { const res = await fetch("/api/signup", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ token }), }); const data = await res.json(); if (!res.ok || data.ok !== true) throw new Error("Submission failed"); // proceed } catch { // surface the error } finally { if (signupWidgetId.current !== null) { window.turnstile.reset(signupWidgetId.current); setToken(""); } } } return ( <>