設定 Turnstile
了解首次部署如何使用測試金鑰,再為正式網域建立 Turnstile Widget、替換金鑰,並確認人機驗證流程。
Turnstile 用來攔截機器人提交表單和惡意嘗試。Saavo 範本的部分功能相當依賴 Turnstile,預設已串接瀏覽器端的 Widget 和伺服器端的 Siteverify 驗證,不需要再複製 Cloudflare 的範例程式碼。
首次部署時,可以先沿用 Cloudflare 官方測試金鑰,完成 Worker 和其他資源的部署。確定正式網域後,再建立 Widget 並換成實際金鑰,避免因為網域尚未決定而無法進行首次部署。
測試金鑰只能用於暫時驗證
Cloudflare 測試金鑰可以在任何主機名稱(Hostname)使用,但不會提供真正的人機辨識能力。可以用它部署到 workers.dev,但正式開放使用者註冊、登入或使用支援表單前,務必換成正式 Widget 產生的金鑰。
何時需要設定
使用以下指令建立專案時,Saavo CLI 不會詢問 Turnstile 金鑰:
npx saavo-cli@latest create my-project建立專案後,本機的 .env 會保留測試金鑰。準備第一次部署到 Cloudflare 時,執行:
npx wrangler login
npm run deploy:initdeploy:init 會完成 Cloudflare 帳戶選擇、Worker 和資源命名、正式環境變數收集,以及首次部署。只要沒有在 config/deploy.ts 關閉 Turnstile,互動流程就會要求輸入:
Cloudflare Turnstile site key:
Cloudflare Turnstile secret key:這兩項不能留白,但部署指令碼不會區分測試金鑰和實際金鑰。如果暫時只有 workers.dev 網址,可以輸入 example.vars 中的官方測試組合。如果已確定正式網域,也已建立 Widget,就直接輸入實際金鑰。兩項值都會寫入 .env.production,不會修改本機的 .env。
正式開放前準備好 Hostname
建立 Widget 時,Cloudflare 至少要求填入一個允許使用的 Hostname。通常會填入產品準備使用的正式網域,例如:
example.com只填主機名稱,不要包含通訊協定、連接埠或路徑。以下寫法都不正確:
https://example.com
example.com:443
example.com/login
*.example.comCloudflare 目前的規則是:填入 example.com 後,它的所有子網域也能使用這個 Widget。如果只填 app.example.com,則不會自動包含 example.com 或其他同層級的子網域。完整規則請參閱 Hostname management。
Saavo 首次部署會先使用 workers.dev 網址。如果只是要確認部署流程,直接使用官方測試金鑰即可,不必為了暫時使用的網址預先建立正式 Widget。準備使用自訂網域後,再將正式網域加入 Hostname Management,並產生實際金鑰。
如果確實要長期提供 workers.dev 網址給使用者,也應為實際的 Hostname 設定正式 Widget,而不是一直使用測試金鑰。Cloudflare 官方文件並未規定所有 workers.dev 網址都不能建立 Widget,是否可加入,請以 Dashboard 當時的驗證結果為準。

建立 Widget
- 登入 Cloudflare Dashboard,切換到部署 Saavo 的帳戶。
- 開啟側邊欄中的 Application security → Turnstile。
- 按下右上角的 Add widget manually。
- 在 Widget name 填入容易辨識的名稱,例如
saavo-production。 - 在 Hostname Management 加入準備使用的正式網域。
- 在 Widget Mode 選擇 Managed。
- 保留 Pre-clearance 的預設設定,按下 Create。

Managed 是 Cloudflare 建議的預設模式,會依存取風險決定是否需要使用者互動。正常使用者大多不需要額外操作。
表單最後的 Pre-clearance 功能可以暫時略過。Saavo 不依賴 Pre-clearance 產生的 cf_clearance Cookie,如果沒有同時設定 WAF Challenge,就不需要啟用這項功能。
Cloudflare 最新的 Dashboard 操作說明請參閱 Create and manage widgets。介面名稱日後可能調整,但核心設定仍是名稱、Hostname 和 Widget Mode。
儲存兩組金鑰
建立 Widget 後,頁面會顯示兩項內容:
| Cloudflare 欄位 | Saavo 環境變數 | 是否可公開 |
|---|---|---|
| Site Key | CLOUDFLARE_TURNSTILE_SITE_KEY | 可以,會傳送到瀏覽器 |
| Secret Key | CLOUDFLARE_TURNSTILE_SECRET_KEY | 不可以,只能保留在伺服器端 |
先將它們存入密碼管理工具或團隊使用的金鑰管理工具。不要將 Secret Key 傳到群組聊天室、寫入 config/deploy.ts,或提交到 Git。
Site Key 和 Secret Key 必須成對使用
測試 Site Key 必須搭配測試 Secret Key,正式 Site Key 則必須搭配同一個 Widget 產生的正式 Secret Key。混用時,頁面可能仍能顯示驗證框,但伺服器端無法通過驗證。
不需要修改本機測試金鑰
從 example.vars 複製的 .env 已包含測試金鑰,不需要修改:
CLOUDFLARE_TURNSTILE_SITE_KEY="3x00000000000000000000FF"
CLOUDFLARE_TURNSTILE_SECRET_KEY="1x0000000000000000000000000000000AA"這些不是需要替換的占位值,而是 Cloudflare 公開提供的測試組合。目前的 Site Key 會強制顯示一次互動式驗證,方便檢查介面,Secret Key 則會讓對應的測試權杖通過伺服器端驗證。
測試金鑰可用於任何開發網域,正式金鑰則會檢查 Widget 允許的 Hostname。Cloudflare 也明確建議不要將 localhost 和 127.0.0.1 加入正式 Widget。測試規則和其他測試組合請參閱 Test your Turnstile implementation。
設定遠端部署
不需要自行執行 wrangler secret put。首次執行 npm run deploy:init 時,在互動流程中輸入成對的 Site Key 和 Secret Key。尚未有正式網域時,可以輸入前面的測試組合。已建立 Widget 時,則輸入實際金鑰。指令會先列出準備寫入的遠端環境變數:
Production environment changes:
- set CLOUDFLARE_TURNSTILE_SITE_KEY
- set CLOUDFLARE_TURNSTILE_SECRET_KEY確認 Write these production environment changes? 後,兩項值會寫入專案根目錄的 .env.production。如果檔案尚未存在,部署指令碼會依 example.vars 的欄位清單建立。如果已經存在,則會保留已填入的值。部署指令碼只檢查兩項是否為空,不會因為使用 Cloudflare 測試金鑰而中止部署。
最後的檔案應包含:
CLOUDFLARE_TURNSTILE_SITE_KEY="測試 Site Key 或實際 Site Key"
CLOUDFLARE_TURNSTILE_SECRET_KEY="與 Site Key 配對的 Secret Key".env.production 是 Saavo 部署流程唯一讀取的正式環境變數檔案,且已由 .gitignore 排除。部署時,指令碼會將 Site Key 作為公開的 Worker 變數,Secret Key 則作為加密 Secret 交給 Wrangler。過程中使用的暫存 Secrets 檔案會在指令結束後刪除。
兩個環境檔案各自管理
.env 用於本機開發,可以一直保留測試金鑰。.env.production 用於遠端部署,首次部署時可以暫用測試金鑰,正式開放前再換成實際金鑰。不要將正式 Secret Key 複製到本機環境,也不要提交這兩個檔案。
如果中途離開或沒有儲存
如果在確認寫入 .env.production 前結束指令,剛才輸入的值不會儲存。處理方式取決於專案是否已完成首次部署:
- 尚未完成首次部署:重新執行
npm run deploy:init,再次輸入成對的兩組金鑰。 - 已完成首次部署:直接修改
.env.production中的兩項值,再執行npm run deploy:update。 - 想在部署前預先準備:可以手動建立或編輯
.env.production,寫入兩組金鑰,再執行npm run deploy:init。如果是手動建立的檔案,也要確認其中的SAAS_SECRET與本機.env完全一致。
不要只在 Cloudflare 後台,或透過 wrangler secret put 修改遠端值。後續執行 npm run deploy:update 仍以 .env.production 為準,只修改遠端環境,會讓專案記錄與實際部署狀態不一致。
修改後,尚未首次部署的專案繼續執行 npm run deploy:init,已初始化的專案則執行 npm run deploy:update。兩個指令都會在實際部署前檢查設定,並同步 Wrangler Secret,但只會檢查是否填入金鑰,不會判斷是否為測試金鑰。
改用自訂網域後替換測試金鑰
確定正式網域後,依序處理:
- 在 Cloudflare 建立 Turnstile Widget,將正式網域加入 Hostname Management。
- 將 Widget 產生的 Site Key 和 Secret Key 寫入
.env.production。 - 如果尚未綁定自訂網域,執行
npm run domain:set -- https://app.example.com。這個指令會一併套用新金鑰並重新部署。 - 如果已綁定網域,只需執行
npm run deploy:update。
部署完成後,使用正式網域實際提交一次註冊或登入表單。確認伺服器端驗證通過,再開放網站給使用者。
了解範本的驗證策略
Saavo 預設不會在每次登入和註冊時都顯示驗證框。config/deploy.ts 中的設定如下:
auth: {
useTurnstile: {
threshold: 2,
interval: '1h',
},
},範本會依身分驗證入口記錄風險分數。達到 threshold 後,下一次請求才會要求完成 Turnstile。驗證成功後,風險分數會降低,一段時間沒有活動也會重設。這樣可以減少正常使用者每次登入都需要驗證的干擾。
如果偵錯時希望身分驗證頁面一律要求驗證,可以暫時改成:
useTurnstile: {
threshold: 0,
interval: '1h',
},測試完成後,再改回預設值。設為 false 只會關閉註冊、登入相關頁面的 Turnstile,並不是整個專案的總開關。客服案件等明確要求人機驗證的表單,仍可能繼續使用 Turnstile。
常見問題
頁面一直沒有顯示驗證框
如果是登入或註冊頁面,請先檢查預設的自適應門檻。風險分數尚未達到 threshold 時,不顯示驗證框是正常行為,不代表 Site Key 沒有生效。偵錯時可以暫時將門檻改成 0。
本機正常,正式網域卻無法載入
先回到 Widget 的 Settings → Hostname Management,檢查瀏覽器網址列中的實際 Hostname 是否在允許範圍內。只填網域,不要填通訊協定、連接埠或路徑。修改 Hostname 後,也要記得儲存設定。
頁面可以顯示,提交後卻提示驗證失敗
通常是 Site Key 和 Secret Key 不屬於同一個 Widget,或混用了測試金鑰和正式金鑰。請重新核對 .env.production 中的兩個變數,再執行 npm run deploy:update。
偶爾出現 timeout-or-duplicate
權杖已超過五分鐘,或同一個權杖被提交兩次。請重新整理驗證框後再提交,不要快取或重複使用舊權杖。
修改環境變數後沒有變化
本機測試讀取 .env,修改後需重新啟動 npm run dev。遠端部署讀取 .env.production,修改後需執行 npm run deploy:update。修改錯誤的檔案,不會自動同步到另一個環境。
完成以上檢查後,就已完成 Turnstile 串接。之後更換網域時,記得先更新 Widget 的 Hostname Management,再將網站流量切換到新網域。