How to Add Cloudflare Turnstile to Your Next.js App: A Complete Guide
A comprehensive, step-by-step guide to implementing Cloudflare Turnstile bot protection in your Next.js application with App Router and Server Actions.
Bot traffic is everywhere. Whether you run a blog or a full application, protecting your forms from automated spam is essential. This guide shows you how to add Cloudflare Turnstile to your Next.js app, a better, privacy-first alternative to reCAPTCHA.
Turnstile uses adaptive challenges. Think of it like airport security:
Trusted traveler (TSA PreCheck): If you have a good reputation and normal behavior, you breeze through with minimal friction, the widget completes instantly in the background
Random screening: If something seems slightly off, you get a simple checkbox to click
Full security check: If multiple red flags appear, you face harder challenges
This happens in real-time based on dozens of signals. Most legitimate users never see a challenge at all.
'use server'import { verifyTurnstileToken } from'@/lib/turnstile'exporttypeContactFormState = {
success: booleanmessage: string
}
exportasyncfunctionsubmitContactForm(prevState: ContactFormState,
formData: FormData): Promise<ContactFormState> {
// Extract the token from form dataconst turnstileToken = formData.get('turnstileToken') asstring// CRITICAL: Verify with Cloudflare before doing anything elseconst isValid = awaitverifyTurnstileToken(turnstileToken)
if (!isValid) {
return {
success: false,
message: 'Verification failed. Please try again.'
}
}
// Token is valid! Safe to process the formconst fullName = formData.get('fullName') asstringconst email = formData.get('email') asstringconst message = formData.get('message') asstring// Validateif (!fullName || !email || !message) {
return {
success: false,
message: 'All fields are required'
}
}
if (!/\S+@\S+\.\S+/.test(email)) {
return {
success: false,
message: 'Invalid email address'
}
}
// Process the form (send email, save to DB, etc.)try {
// Your business logic here// await sendEmail({ fullName, email, message })return {
success: true,
message: 'Message sent successfully!'
}
} catch (error) {
console.error('Form submission error:', error)
return {
success: false,
message: 'Something went wrong. Please try again.'
}
}
}
What happens during verification:
Token Extraction: We pull the token from FormData (remember the hidden input?)
Server → Cloudflare: Our verifyTurnstileToken function sends:
hljsCopy
{"secret":"your_secret_key","response":"0.aB1cD2eF3gH4..."// The user's token}
Cloudflare Checks: Their servers validate:
Is this token signature valid?
Was it issued by us (matching site key)?
Has it been used before? (one-time use)
Is it expired? (5-minute window)
Does it match the expected challenge difficulty?
Response: Cloudflare returns:
hljsCopy
{"success":true,// or false"error-codes":[]// or ["timeout-or-duplicate", etc.]}
Decision: If success: true, we proceed. If false, we reject the submission.
Why verify server-side? Client-side checks can be bypassed. A bot could send any token or skip the widget entirely. Server-side verification ensures Cloudflare confirms the token is legitimate before you process sensitive operations.
You've successfully added bot protection to your Next.js forms with Turnstile. The implementation is clean, the user experience is smooth, and your users' privacy is protected.