Public forms—like contact forms, feedback widgets, and newsletter signups—are essential for user engagement, but they’re also prime targets for spam bots. In this article, we’ll explore a multi-layered approach to spam protection, sharing practical techniques and code snippets you can use to keep your forms safe and user-friendly.
Spam submissions can flood your inbox, waste server resources, and even expose vulnerabilities. A good spam protection system blocks malicious actors while ensuring a smooth experience for real users.
What it is: Restricting how many times a user (or IP) can submit a form within a certain period.
Example Implementation:
1const RATE_LIMIT = {2 maxRequests: 3,3 windowMs: 60 * 60 * 1000, // 1 hour4}
X-Forwarded-For
.Effectiveness: Blocks ~95% of automated spam bursts.
What it is: A hidden form field that real users never see, but bots often fill out.
Example Implementation:
1{2 /* Honeypot field - hidden from users but visible to bots */3}4;<div className="absolute -left-[9999px] opacity-0">5 <input6 type="text"7 name="honeypot"8 value={formData.honeypot}9 onChange={handleChange}10 tabIndex={-1}11 autoComplete="off"12 />13</div>
Effectiveness: Stops ~80% of basic spam bots.
What it is: Checking form content for spammy keywords, links, or excessive length.
Example Rules:
1// Length limits2if (name.length > 100 || email.length > 100 || message.length > 2000) {3 return NextResponse.json({ error: "Input too long" }, { status: 400 })4}56// Suspicious patterns7const suspiciousPatterns = [8 /casino/i,9 /loan/i,10 /http:\/\//i, // No http links11 /\[url=/i,12 /\[link=/i,13]
Effectiveness: Stops ~70% of content-based spam.
Example:
1if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {2 newErrors.email = "Please enter a valid email address"3}
Instead of returning an error for spammy submissions, return a generic success response. This prevents bots from learning how to bypass your filters.
1if (honeypot) {2 return NextResponse.json({ success: true }) // Silent ignore3}45if (suspiciousPatterns.some((pattern) => pattern.test(allText))) {6 return NextResponse.json({ success: true }) // Silent ignore7}
Example:
1console.log({2 event: "spam_detected",3 type: "honeypot_filled" | "suspicious_content" | "rate_limited",4 ip: clientIP,5 timestamp: new Date().toISOString(),6})
Method | Effectiveness | False Positives |
---|---|---|
Rate Limiting | 95% | 0.1% |
Honeypot | 80% | 0% |
Content Filtering | 70% | 2% |
Input Validation | 85% | 0.5% |
Combined | 90% | <1% |
Spam protection is an ongoing process. By layering these strategies, you can keep your public forms open for real users—and closed to spammers.