Next.js/React集成Cloudflare Turnstile表单无法获取响应令牌求助
问题:Next.js 13 集成Cloudflare Turnstile后无法获取验证令牌
我是Next.js/React新手,正在构建简易联系表单并使用Cloudflare Turnstile防护。未集成Turnstile时,表单可正常提交到API并发送邮件,但集成后无法获取响应令牌——检查元素能看到输入框已填充令牌,但提交时token始终返回null。
环境
- Next.js 13(使用
'use client'指令) - 依赖:
@marsidev/react-turnstile
表单代码
<form onSubmit={handleSubmit}> <div className="flex flex-col gap-4"> <div className="form-control w-full max-w-md"> <label className="label" htmlFor="name"> <span className="label-text">Name</span> </label> <input type="text" id="name" placeholder="Your Name" required className="input w-full max-w-md" name="name" onChange={(e) => setName(e.target.value)} /> </div> <div className="form-control w-full max-w-md"> <label className="label" htmlFor="email"> <span className="label-text">Email</span> </label> <input type="text" id="email" placeholder="Your Email" required className="input w-full max-w-md" name="email" onChange={(e) => setEmail(e.target.value)} /> </div> <div className="form-control w-full max-w-md"> <label className="label" htmlFor="subject"> <span className="label-text">Subject</span> </label> <input type="text" id="subject" placeholder="Subject" required className="input w-full max-w-md" name="subject" onChange={(e) => setSubject(e.target.value)} /> </div> <div className="form-control"> <label className="label" htmlFor="message"> <span className="label-text">Message</span> </label> <textarea className="textarea-bordered textarea h-48 w-full max-w-md" id="message" placeholder="Your message" required name="message" onChange={(e) => setMessage(e.target.value)} ></textarea> </div> <Turnstile siteKey={cfSiteKey} /> <button type="submit" className="btn-primary btn w-full max-w-md"> Submit </button> </div> </form>
处理函数代码
let cfSiteKey: string; const [name, setName] = useState(''); const [email, setEmail] = useState(''); const [subject, setSubject] = useState(''); const [message, setMessage] = useState(''); const [submitted, setSubmitted] = useState(false); if (process.env.NEXT_PUBLIC_CLOUDFLARE_TURNSTILE_SITE_KEY) { cfSiteKey= process.env.NEXT_PUBLIC_CLOUDFLARE_TURNSTILE_SITE_KEY; } else { throw new Error('Cloudflare Turnstile Secret Key Not Set'); } const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); const formData = new FormData(); const token = formData.get('cf-turnstile-response'); console.log(token); // 始终返回null const data = { name, email, subject, message, token, }; fetch('/api/contact', { method: 'POST', headers: { Accept: 'application/json, text/plain, */*', 'Content-Type': 'application/json', }, body: JSON.stringify(data), }).then((res) => { console.log('Response received'); if (res.status === 200) { console.log('Response succeeded!'); setSubmitted(true); setName(''); setEmail(''); setSubject(''); setMessage(''); } }); };
解决方法
核心问题分析
你创建FormData时没有传入表单元素,导致生成的FormData是空对象,自然无法获取到cf-turnstile-response字段的值。另外,更可靠的方式是直接通过Turnstile组件的回调来获取令牌,避免依赖FormData的自动收集。
方法一:修复FormData的创建逻辑
在handleSubmit中,将表单元素传入FormData构造函数,这样它会自动收集所有表单字段:
const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); // 传入表单元素到FormData,自动收集所有字段 const formData = new FormData(e.currentTarget as HTMLFormElement); const token = formData.get('cf-turnstile-response'); console.log(token); // 现在能正确获取令牌 // 后续逻辑不变... };
方法二:通过Turnstile组件的onChange回调保存令牌(更推荐)
这种方式直接从组件获取令牌,无需依赖FormData,逻辑更清晰可靠:
- 新增状态存储令牌:
const [token, setToken] = useState('');
- 修改Turnstile组件,添加
onChange回调:
<Turnstile siteKey={cfSiteKey} onChange={setToken} />
- 在
handleSubmit中直接使用token状态:
const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); // 先校验令牌是否存在 if (!token) { console.error('请完成Turnstile验证'); return; } const data = { name, email, subject, message, token, }; fetch('/api/contact', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(data), }) .then((res) => { if (res.ok) { setSubmitted(true); setName(''); setEmail(''); setSubject(''); setMessage(''); setToken(''); // 重置令牌状态 } else { console.error('提交失败'); } }) .catch(err => console.error('提交出错:', err)); };
额外优化点
- 简化
cfSiteKey的获取方式,直接使用断言(因为是public环境变量,构建时必须存在):
const cfSiteKey = process.env.NEXT_PUBLIC_CLOUDFLARE_TURNSTILE_SITE_KEY!;
- 给fetch添加
catch处理,避免未捕获的错误导致页面无响应。
内容的提问来源于stack exchange,提问作者BWBama85
相关产品推荐
相关产品推荐

