Hentikan spam formulir kontak6 menit baca
Cara menghentikan spam di formulir kontak situs statis
Hentikan spam di formulir situs statis meskipun situs itu tidak bisa menyimpan secret atau menghitung kiriman, dengan partial honeypot, skrip yang aman, dan pemeriksaan sisi server.
Situs statis tidak punya server, jadi tidak ada apa pun di halaman yang bisa menyimpan secret key, memverifikasi token captcha, atau menghitung kiriman per pengunjung. Pemeriksaan itu harus berjalan di tempat lain: function di hosting Anda, atau form backend tujuan kiriman formulir. Apa pun pilihannya, pasang honeypot yang aksesibel di formulir, dan pastikan formulir tetap berfungsi tanpa JavaScript.
Apa yang berbeda di situs statis
Di situs statis, setiap byte yang Anda deploy bersifat publik. Apa pun di HTML atau JavaScript Anda bisa dibaca dan dilewati bot, jadi pemeriksaan spam di sisi client hanya saran, bukan penegakan. Tiga pemeriksaan yang paling penting semuanya butuh server:
- Memverifikasi token captcha butuh secret key, yang tidak bisa disimpan di file publik.
- Menghitung kiriman per pengunjung butuh penyimpanan yang bertahan di antara request.
- Membaca honeypot butuh kode yang berjalan setelah kiriman tiba, di luar jangkauan bot.
Hosting statis juga tidak menerima kiriman ke halaman Anda. Vercel dan GitHub Pages menjawab POST formulir ke halaman statis dengan 405 Method Not Allowed, jadi formulir butuh endpoint bahkan sebelum soal spam muncul.
- Halaman statis yang mengirim ke dirinya sendiri sampai ke hosting yang tidak menjalankan kode, sehingga kiriman gagal dengan 405 atau hilang begitu saja.
- Dengan function di hosting, halaman mengirim ke function yang menyimpan secret, membaca honeypot, dan memverifikasi token, lalu meneruskan pesannya.
- Dengan form backend, halaman mengirim langsung ke backend, yang menjalankan honeypot, skor, rate limit, dan pemeriksaan manusia sebelum mengirimi Anda email.
Pilihan Anda
| Opsi | Pemeriksaan spam | Kekurangan |
|---|---|---|
| Netlify Forms | Akismet di setiap kiriman, ditambah field netlify-honeypot dan reCAPTCHA v2 jika Anda menambahkannya | Hanya di Netlify. Pindah hosting berarti membangun ulang formulir. Lihat alternatif Netlify Forms |
| Function di hosting Anda | Apa pun yang Anda tulis: honeypot, pemeriksaan token, rate limit dengan penyimpanan | Anda yang menulis dan memeliharanya, dan butuh layanan email atau penyimpanan di belakangnya |
| Form backend, seperti Ikiform | Honeypot, skor, rate limit, domain yang diizinkan, dan pemeriksaan manusia, tanpa kode | Layanan tambahan, dengan harga per kiriman |
Formulir sebagai partial yang bisa dipakai ulang
Endpoint mana pun yang Anda pilih, tulis formulir sekali sebagai partial atau include, agar setiap halaman memakai honeypot yang sama. Versi di bawah mengirim ke Ikiform. Untuk memakai function, ubah action ke path function dan hapus _redirect.
<form action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="POST" class="contact">
<label for="email">Email</label>
<input id="email" name="email" type="email" autocomplete="email" required />
<label for="message">Message</label>
<textarea id="message" name="message" rows="5" required></textarea>
<div class="hp" aria-hidden="true">
<label for="hp-field">Leave this empty</label>
<input id="hp-field" name="_gotcha" type="text" tabindex="-1" autocomplete="off" />
</div>
<input type="hidden" name="_redirect" value="{{ "thanks/" | absURL }}" />
<p role="status"></p>
<button>Send message</button>
</form><style>
.hp {
position: absolute;
left: -10000px;
width: 1px;
height: 1px;
overflow: hidden;
}
</style>- Honeypot diletakkan di luar layar, di luar urutan tab, dan disembunyikan dari pembaca layar. Panduan honeypot menjelaskan setiap atributnya.
_redirectmengarahkan orang ke halaman terima kasih Anda. Ikiform hanya mengikutinya untuk alamathttps:di salah satu Allowed domains formulir.- Panduan Hugo, Jekyll, dan Eleventy membahas setup, shortcode, dan GitHub Pages.
Tingkatkan tanpa merusaknya
Skrip bisa membuat pengunjung tetap di halaman, tetapi tidak boleh menjadi satu-satunya cara formulir berfungsi. Skrip ini mengirim dengan fetch dan kembali ke kiriman browser biasa dalam dua kasus: saat panggilan jaringan gagal, dan saat pemeriksaan manusia Ikiform meminta token dengan challenge_required. Kiriman biasa mendapat halaman pemeriksaan Ikiform, jadi Anda bisa menyalakan pemeriksaan tanpa mengubah skrip.
<script>
document.querySelectorAll("form.contact").forEach((form) => {
const status = form.querySelector("[role=status]");
const button = form.querySelector("button");
form.addEventListener("submit", async (event) => {
event.preventDefault();
button.disabled = true;
try {
const response = await fetch(form.action, {
method: "POST",
headers: { Accept: "application/json" },
body: new FormData(form),
});
const result = await response.json();
if (result.ok) {
form.replaceChildren("Thanks. We'll reply by email.");
return;
}
if (result.error.code === "challenge_required") {
form.submit();
return;
}
const wait = response.headers.get("Retry-After");
if (response.status === 429 && wait) {
status.textContent = `Too many messages. Try again in ${wait} seconds.`;
} else {
status.textContent = result.error.message;
}
} catch {
form.submit();
return;
}
button.disabled = false;
});
});
</script>form.submit()mengirim formulir tanpa memicu eventsubmitlagi, sehingga fallback tidak bisa berulang tanpa henti.429menampilkan berapa lama harus menunggu dariRetry-After, yang dibuka Ikiform untuk kode browser.- Kalimat sukses menggantikan formulir, sehingga tidak ada yang mengirimnya dua kali tanpa sengaja.
Jika Anda menulis function di hosting
Netlify, Cloudflare Pages, dan Vercel semuanya menjalankan function kecil di samping file statis. Function adalah backend Anda sendiri, jadi ia yang memegang pemeriksaan spam. Netlify Function ini membaca honeypot, membuang pesan yang penuh link, memverifikasi token Turnstile, dan mengirimi Anda email lewat Resend. Setiap kiriman yang tertangkap mendapat redirect terima kasih yang sama dengan kiriman asli.
import type { Config, Context } from "@netlify/functions";
const LINK = /https?:\/\/|www\./gi;
export default async (req: Request, context: Context) => {
if (req.method !== "POST") return new Response("Method not allowed", { status: 405 });
const form = await req.formData();
const email = String(form.get("email") ?? "");
const message = String(form.get("message") ?? "");
const thanks = Response.redirect(new URL("/thanks/", req.url), 303);
if (String(form.get("_gotcha") ?? "").trim()) return thanks;
if ((message.match(LINK)?.length ?? 0) >= 3) return thanks;
const check = await fetch("https://challenges.cloudflare.com/turnstile/v0/siteverify", {
method: "POST",
body: new URLSearchParams({
secret: process.env.TURNSTILE_SECRET_KEY ?? "",
response: String(form.get("cf-turnstile-response") ?? ""),
remoteip: context.ip,
}),
});
const verdict = (await check.json()) as { success: boolean };
if (!verdict.success) return Response.redirect(new URL("/contact/?error=check", req.url), 303);
await fetch("https://api.resend.com/emails", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.RESEND_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
from: "Contact form <form@example.com>",
to: "you@example.com",
reply_to: email,
subject: "New message from the website",
text: message,
}),
});
return thanks;
};
export const config: Config = { path: "/api/contact" };context.ipadalah IP pengunjung, yang bisa dipakai siteverify sebagairemoteip.- Atur
TURNSTILE_SECRET_KEYdanRESEND_API_KEYdi environment variable situs, jangan pernah di repository. - Function ini membuang kiriman yang tertangkap alih-alih menyimpannya, jadi pesan asli yang salah tertangkap akan hilang. Setidaknya catat di log.
- Function ini tidak punya rate limit, karena function tidak punya memori di antara pemanggilan. Panduan rate limiting menambahkannya dengan Redis.
Yang dilakukan Ikiform untuk formulir statis
Dengan formulir yang mengirim langsung ke Ikiform, pekerjaan sisi server terjadi di sana. Honeypot-nya _gotcha, skor konten dan rate limit per pengunjung berjalan di setiap kiriman, dan Allowed domains menandai kiriman dari situs lain sebagai spam. Atur Spam check ke Ikiform untuk Cloudflare Turnstile tanpa key: kiriman HTML biasa menjalankannya di halaman pemeriksaan, dan skrip di atas kembali ke halaman itu. Kiriman yang dinilai spam mendapat jawaban sukses yang sama dengan kiriman asli, jadi bot tidak belajar apa-apa. Kiriman itu disimpan dengan status spam, tidak mengirim email notifikasi maupun webhook, tidak dihitung dalam pemakaian Anda, dan dihapus setelah 30 hari. Setiap Senin, workspace yang memblokir spam mendapat satu email ringkasan berisi total, formulir tersibuk, dan alasan teratas.
username.github.io. Deploy preview Netlify dan URL preview Vercel bukan subdomain dari alamat situs utama Anda; tambahkan host preview yang persis selama Anda mengujinya.Pastikan berfungsi
- Matikan JavaScript dan kirim. Formulir tetap terkirim dan Anda mendarat di halaman terima kasih.
- Nyalakan JavaScript dan kirim. Formulir digantikan oleh kalimat terima kasih.
- Kirim dengan honeypot terisi,
_gotcha=bot. Kiriman tetap mendapat jawaban sukses, tetapi disimpan sebagai spam dan tidak memicu email atau webhook. - Atur Spam check ke Ikiform dan kirim dengan skrip aktif. Halaman kembali ke kiriman biasa dan menampilkan halaman pemeriksaan.
- Tidak ada yang masuk? Ubah filter status di dashboard ke Spam. Spam mendapat jawaban sukses yang sama dengan kiriman asli.
Hal yang sering menjebak
403 origin_not_allowed, atau setiap kiriman masuk ke spam. Domain halaman tidak ada di Allowed domains formulir. Tambahkan, ditambahlocalhostuntuk uji lokal. Subdomain dari domain yang terdaftar diizinkan.- HTML yang di-minify. Beberapa langkah build menghapus atribut yang dianggap kosong atau tidak terpakai. Pastikan halaman hasil build masih punya
aria-hiddendantabindexpada honeypot. - Halaman terima kasih yang di-cache. Jika halaman terima kasih memakai URL yang sama untuk spam dan kiriman asli, dan memang seharusnya begitu, jangan pernah menaruh apa pun di sana yang bergantung pada keaslian kiriman.
- Key di repository. Secret yang di-commit ke repository situs statis publik ikut menjadi publik. Rotasi secret itu jika pernah terjadi.
Pertanyaan
Anda bisa menampilkan widget-nya, tetapi tokennya harus diverifikasi dengan secret key di server. Pakai function di hosting Anda, form backend yang memverifikasi key Anda, atau pemeriksaan terkelola Ikiform, yang tidak butuh key.
Honeypot menghentikan bot yang paling umum, tetapi hanya jika ada sesuatu di server yang membacanya. Pasangkan dengan rate limit dan skor konten, yang juga butuh server atau form backend.
Formulir mengirim ke halaman di hosting yang hanya menyajikan file. Arahkan action formulir ke function atau endpoint form backend sebagai gantinya.
Seharusnya ya. Kirim dengan formulir HTML biasa terlebih dulu, lalu tambahkan skrip yang menyempurnakannya dan kembali ke kiriman biasa saat terjadi masalah.
Mentok di salah satu langkah, atau ingin panduan untuk situasi Anda? Email hello@ikiform.com.