Cloudflare Turnstile でフォームを保護する
Cloudflare Turnstile は、プライバシーを重視した最新の CAPTCHA 代替手段です。ユーザーへの問題提示なしに摩擦のないボット検出を提供し、アクセシビリティとフォームセキュリティの両方を向上させます。
Formspree は Turnstile をすぐに使える形でサポートしています。キーを設定してフォーム設定で CAPTCHA を有効にするだけです。
ステップ 1: Turnstile サイトを作成する
まず Cloudflare Turnstile ダッシュボード にアクセスします。
Add widget をクリックして新しい Turnstile サイトを作成します。

- サイトの name(名前)を入力します。
- Allowed Domains にあなたの domain(例:
example.com)を追加します。開発目的には localhost を使用してください。 - widget mode(ウィジェットモード)を選択します。

設定を保存します。作成完了後、新しいウィジェットキーが表示されます。

この Site Key と Secret Key をコピーします。HTML フォームと Formspree の設定で使用します。
ステップ 2: Formspree で CAPTCHA を設定する
Formspree ダッシュボード でフォームの設定ページに移動します。
CAPTCHA セクションで CAPTCHA 保護が enabled(有効)になっていることを確認します。
Adjust settings をクリックし、Cloudflare Turnstile を選択して、提供されたフィールドに Secret Key を貼り付けます。

変更を保存します。
ステップ 3: フォーム HTML に Turnstile を追加する
次に、フォームに Turnstile を追加します。HTML コードに Turnstile スクリプトと Site Key を含めます。
基本的な例を以下に示します。
<html>
<head>
<title>Turnstile Demo</title>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
</head>
<body>
<form action="https://formspree.io/f/{your-form-id}" method="POST">
<input type="email" name="email" placeholder="Email" required />
<div class="cf-turnstile" data-sitekey="{your-site-key}"></div>
<br />
<input type="submit" value="Submit" />
</form>
</body>
</html>
ユーザーがフォームを送信すると、Turnstile ウィジェットが自動的にトークン(cf-turnstile-response)を生成し、Formspree があなたの Secret Key を使って検証します。
AJAX の使用(オプション)
AJAX でフォームを送信する場合、リクエストに Turnstile トークンを手動で含めることができます。
以下は Vanilla JS を使用した例です。
<!DOCTYPE html>
<html>
<head>
<title>Turnstile AJAX Demo</title>
<!-- Turnstile script -->
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
</head>
<body>
<form id="form" method="POST" action="https://formspree.io/f/<form-hashid>">
<input type="email" name="email" placeholder="email@example.com" required />
<!-- Turnstile widget (automatically adds hidden cf-turnstile-response input) -->
<div class="cf-turnstile" data-sitekey="{site-key}"></div>
<br />
<button type="submit">Submit</button>
</form>
<script>
const form = document.getElementById("form");
form.addEventListener("submit", function (event) {
event.preventDefault();
// Grab all form fields, including the hidden cf-turnstile-response
const data = new FormData(form);
fetch(form.action, {
method: form.method,
body: data,
headers: {
"Accept": "application/json"
}
})
.then(function (response) {
return response.json();
})
.then(function (data) {
console.log("Formspree response:", data);
})
.catch(function (error) {
console.error("Error:", error);
});
});
</script>
</body>
</html>
または Turnstile API を使用することもできます。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<script
src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit"
defer
></script>
</head>
<body>
<form id="contact-form">
<input type="email" name="email" placeholder="Email" required />
<div id="turnstile-container"></div>
<button type="submit">Send</button>
</form>
<script>
let widgetId = null;
// Render widget when the page finishes loading
window.onload = function () {
if (!window.turnstile) {
console.error("Turnstile did not load.");
return;
}
widgetId = window.turnstile.render("#turnstile-container", {
sitekey: "{your-site-key}",
callback: function (token) {
console.log("Turnstile success:", token);
}
});
};
// AJAX submit
document.getElementById("contact-form").addEventListener("submit", function (event) {
event.preventDefault();
const form = this;
const formData = new FormData(form);
// Ensure we send the token under the standard field name
// If Turnstile already added a hidden input, this overwrites it.
formData.set("cf-turnstile-response", token);
fetch("/submit", {
method: "POST",
body: formData
})
.then((res) => {
if (!res.ok) {
throw new Error("Server error: " + res.status);
}
return res.text();
})
.then((text) => {
alert("Server replied: " + text);
// Reset for next submission
window.turnstile.reset(widgetId);
form.reset();
})
.catch((err) => {
console.error(err);
alert("Request failed: " + err.message);
});
});
</script>
</body>
</html>