跳转到主要内容
vibestrap 用 Cloudflare Turnstile 保护公开表单—— 一个 CAPTCHA 替代品,隐私友好、免费、不下 cookie。Widget 已经接到注册、忘记密码、订阅这三个表单上; 服务端校验工具在 src/lib/turnstile.ts,会在 action 跑之前先验 token。如果 env 没配, 校验会优雅地返回 { ok: true, skipped: true },开发环境和未配置部署都能照常工作。

为啥选 Turnstile(而不是 reCAPTCHA)

  • 隐私友好。不下 cookie、不接 Google 跟踪,开箱即合 GDPR。
  • 任意流量免费——没有用量上限。
  • 用户摩擦小——大部分挑战都是隐形的(managed 模式)。
  • Cloudflare 生态——已经走 Cloudflare 代理的话,没什么好犹豫的。

前置条件

  • Cloudflare 账号(免费版就够)。
  • dash.cloudflare.com → Turnstile → Add Site 把域名注册成 Turnstile site。
  • 从该站点设置页拿到 site key 和 secret key。

一步步配置

  1. 在 Cloudflare dashboard 创建一个 Turnstile site。把生产域名加进去, 再加上 localhost 用于本地开发。模式选 “Managed”(Cloudflare 自己决定要不要挑战, 用户体验最好)。
  2. .env.local 配两个 env 变量:
    NEXT_PUBLIC_TURNSTILE_SITE_KEY=0x4AAAAAAA...     # 公开,会打到客户端
    TURNSTILE_SECRET_KEY=0x4AAAAAAA...                # 服务端,永不暴露
    
  3. 确认 src/config/site.ts 的 feature flag(默认就是开的):
    features: {
      enableTurnstile: true,
    },
    
  4. 重启 pnpm dev。Widget 会出现在注册、忘记密码、订阅表单上。 Server action 会自动调 verifyTurnstile(token),token 缺失或非法直接拒绝。

验证生效

  1. 无痕打开 /register——能看到 Turnstile widget(通常是隐形的,或一闪 “verifying”)。
  2. 打开 devtools → Network。提交时表单会带 cf-turnstile-response 字段。 Server action 内部调 https://challenges.cloudflare.com/turnstile/v0/siteverify,浏览器侧看不到这个外联请求。
  3. NEXT_PUBLIC_TURNSTILE_SITE_KEY 删掉再试——widget 消失。服务端这边 turnstileEnabled() 返回 false,校验直接 skip(返回 { ok: true, skipped: true })。
  4. 用 devtools 篡改 token → server action 报错,表单不提交。

常见坑

  1. token 只能用一次。如果 server action 内部用同一个 token 重试(比如 DB 抖动后重试), 第二次就会失败。要么提交失败时重置 widget,要么在 action 入口短路。
  2. token 大约 5 分钟过期。长寿命表单页(多步注册之类)提交前要刷新一下 widget。 调 turnstile.render 时设个 expired-callback,Turnstile JS 会自动续。
  3. 不要只信客户端。绿色对勾没有服务端校验就是表演。verifyTurnstile() 才是真正在 保护你的,自己写 server action 时千万别绕开。
  4. 生产忘配 token。常见原因:TURNSTILE_SECRET_KEY 配了,但 NEXT_PUBLIC_TURNSTILE_SITE_KEY 没部署(托管平台的 env 没加)。后果:widget 不出现 → 没有 token → 服务端拒绝所有注册。两个 env 必须一起部署。
  5. localhost 没加白名单。要把 localhost(如果你用 127.0.0.1 也加上) 加到 Turnstile site 的域名列表,否则本地注册会静默失败。

怎么接的

// src/lib/turnstile.ts(节选)
export async function verifyTurnstile(token: string | undefined | null) {
  if (!siteConfig.features.enableTurnstile || !env.TURNSTILE_SECRET_KEY) {
    return { ok: true, skipped: true };
  }
  if (!token) return { ok: false, skipped: false, errors: ['missing-token'] };
  // POST 到 Cloudflare siteverify,把结果解析返回。
}
Server action 第一件事就是调 verifyTurnstile(input.cfTurnstileToken)!ok 直接短路。 要给新表单加保护:客户端渲染 widget,把 token 通过 action 的 input schema 传过来, action body 里调一下 verifyTurnstile 即可。

怎么关掉

二选一:
  • siteConfig.features.enableTurnstile = false(编译期关),或
  • 两个 env 变量都不配(运行时 skip)。
任意一种都会让 verifyTurnstile 返回 { ok: true, skipped: true },action 后面照常走。

官方文档