A comprehensive Next.js application demonstrating how to prevent bot traffic from inflating your Braze Monthly Active Users (MAU) by implementing multi-factor bot detection before initializing the Braze Web SDK.
Bot traffic can artificially inflate your Braze MAU count, leading to:
- Inaccurate analytics and metrics
- Higher costs (if pricing is based on MAU)
- Skewed user behavior data
- Poor data-driven decision making
This application implements a multi-layered bot detection system that:
- Analyzes requests at the edge using Next.js middleware
- Uses multiple detection factors: User-Agent, HTTP headers, IP addresses, browser fingerprinting
- Prevents SDK initialization for detected bots
- Maintains user experience for legitimate users
┌─────────────┐
│ Request │
└──────┬──────┘
│
▼
┌──────────────────┐
│ Next.js │
│ Middleware │◄─── Multi-factor bot detection
└──────┬───────────┘
│
├─── Sets cookie: { isBot: boolean, confidence: number }
│
▼
┌──────────────────┐
│ Client-Side │
│ Page Render │
└──────┬───────────┘
│
▼
┌──────────────────┐
│ Check Cookie │
└──────┬───────────┘
│
├─── isBot = true ──→ ❌ Skip Braze initialization
│
└─── isBot = false ──→ ✅ Initialize Braze SDK
When a request hits the Next.js middleware, the detection process follows these steps:
The middleware extracts key information from the incoming request:
// middleware.ts
const fingerprint = createFingerprintFromHeaders(request.headers);
const clientIP = fingerprint.ip || 'unknown';// lib/bot-detection/detector.ts
export function createFingerprintFromHeaders(headers: Headers): RequestFingerprint {
return {
userAgent: headers.get('user-agent') || '',
ip: extractIP(headers),
acceptLanguage: headers.get('accept-language') || undefined,
acceptEncoding: headers.get('accept-encoding') || undefined,
accept: headers.get('accept') || undefined,
secFetchSite: headers.get('sec-fetch-site') || undefined,
secFetchMode: headers.get('sec-fetch-mode') || undefined,
// ... other headers
};
}Three independent analyzers evaluate the request:
| Analyzer | What it checks | Confidence weight |
|---|---|---|
| User-Agent | Bot patterns, browser indicators, isbot library |
High (0.95 for known bots) |
| Headers | Missing/suspicious headers, browser-likeness score | Medium (up to 0.50) |
| IP Address | Cloud provider ranges, datacenter IPs, invalid addresses | Low (0.30-0.40) |
User-Agent Analysis:
// lib/bot-detection/user-agent-detector.ts
export function analyzeUserAgent(userAgent: string | undefined): UserAgentAnalysis {
// Check with isbot library (comprehensive bot database)
if (isbot(userAgent)) {
return { isBot: true, confidence: 0.95, reasons: ['Detected by isbot library'] };
}
// Check custom bot patterns
const BOT_PATTERNS = [/curl/i, /python-requests/i, /axios/i, /puppeteer/i, ...];
for (const pattern of BOT_PATTERNS) {
if (pattern.test(userAgent)) {
return { isBot: true, confidence: 0.85, reasons: [`Matched: ${pattern}`] };
}
}
// Check for incomplete user-agents
if (userAgent.length < 20) {
return { isBot: true, confidence: 0.6, reasons: ['User agent too short'] };
}
return { isBot: false, confidence: 0, reasons: [] };
}Header Analysis:
// lib/bot-detection/header-analyzer.ts
export function analyzeHeaders(fingerprint: RequestFingerprint): HeaderAnalysis {
let suspicionScore = 0;
if (!fingerprint.accept) suspicionScore += 20;
if (!fingerprint.acceptLanguage) suspicionScore += 15;
if (!fingerprint.acceptEncoding) suspicionScore += 15;
if (fingerprint.accept === '*/*') suspicionScore += 20; // Generic = bot-like
if (!fingerprint.secFetchSite) suspicionScore += 15; // Missing modern browser headers
const confidence = Math.min(suspicionScore / 100, 1);
return { isBot: confidence >= 0.5, confidence, reasons };
}IP Analysis:
// lib/bot-detection/ip-analyzer.ts
export function analyzeIP(ip: string | undefined): IPAnalysis {
// Check cloud provider IP ranges (AWS, GCP, Azure, DigitalOcean)
const CLOUD_PATTERNS = [/^3\./, /^13\./, /^35\./, /^52\./, ...];
for (const pattern of CLOUD_PATTERNS) {
if (pattern.test(ip)) {
return { isBot: false, confidence: 0.4, reasons: ['Cloud provider IP'] };
}
}
if (ip === '0.0.0.0' || ip === '::') {
return { isBot: true, confidence: 0.8, reasons: ['Invalid IP address'] };
}
return { isBot: false, confidence: 0, reasons: [] };
}// lib/bot-detection/detector.ts
// Use max confidence if any method strongly detects, otherwise average
const finalConfidence = maxConfidence > 0.5 ? maxConfidence : averageConfidence;This ensures a single strong signal (e.g., known bot user-agent) isn't diluted by weaker signals.
// Normal mode: 60% threshold
isBot = finalConfidence >= 0.6;
// Strict mode: lower threshold + multiple reasons
isBot = finalConfidence >= 0.4 || reasons.length >= 3;The middleware sets three headers on every response:
// middleware.ts
response.headers.set('x-bot-detected', analysis.isBot.toString());
response.headers.set('x-bot-confidence', analysis.confidence.toString());
response.headers.set('x-client-ip', clientIP);These headers (plus a cookie) allow client-side code to check bot status and conditionally initialize SDKs.
- Uses the
isbotlibrary (comprehensive bot database) - Custom bot patterns (crawlers, scrapers, headless browsers)
- Validates browser indicators (Chrome, Firefox, Safari)
- Checks for suspicious patterns and lengths
- Accept header: Bots often use
*/*instead of browser-specific values - Accept-Language: Missing or malformed indicates non-browser
- Accept-Encoding: Legitimate browsers support gzip/brotli
- Sec-Fetch-* headers: Modern browser security features
- Connection header:
closemore common in bots - Upgrade-Insecure-Requests: Present in legitimate browsers
- Identifies cloud provider IPs (AWS, GCP, Azure, DigitalOcean)
- Detects datacenter vs residential IPs
- Flags invalid or suspicious IP addresses
- Handles proxy headers (X-Forwarded-For, X-Real-IP, CF-Connecting-IP)
- Language preferences
- Platform information
- Vendor strings
- Cookie support
- Overall "browser-likeness" scoring
- Clone or create the project:
npm install- Set up environment variables:
Create a
.env.localfile:
NEXT_PUBLIC_BRAZE_API_KEY=your_braze_api_key_here
NEXT_PUBLIC_BRAZE_SDK_ENDPOINT=your_braze_sdk_endpoint_here
NEXT_PUBLIC_BRAZE_APP_VERSION=1.0.0
BOT_DETECTION_STRICT_MODE=false- Run the development server:
npm run dev- Open your browser: Visit http://localhost:3000
The bot detection happens automatically via middleware. In your components:
'use client';
import { useBraze } from '@/lib/braze';
export default function MyComponent() {
const brazeConfig = {
apiKey: process.env.NEXT_PUBLIC_BRAZE_API_KEY || '',
sdkEndpoint: process.env.NEXT_PUBLIC_BRAZE_SDK_ENDPOINT || '',
enableLogging: true,
};
// Automatically checks for bots and conditionally initializes
const { initialized, isBot, confidence } = useBraze(brazeConfig);
return (
<div>
{isBot ? (
<p>Bot detected - Braze not initialized</p>
) : (
<p>Braze initialized successfully!</p>
)}
</div>
);
}If you prefer manual control:
import { initializeBraze, getBotDetectionFromCookie } from '@/lib/braze';
// Check bot status
const detection = getBotDetectionFromCookie();
if (!detection?.isBot) {
// Initialize Braze only for humans
const result = initializeBraze({
apiKey: 'your_api_key',
sdkEndpoint: 'your_endpoint',
});
console.log('Braze initialized:', result.initialized);
}Use the safe wrapper functions that check initialization status:
import { logCustomEvent, changeUser, logPageView } from '@/lib/braze';
// These only execute if Braze is initialized (i.e., not a bot)
logCustomEvent('button_clicked', { button_id: 'signup' });
changeUser('user123');
logPageView('/pricing');Enable more aggressive bot detection:
BOT_DETECTION_STRICT_MODE=trueIn strict mode:
- Lower confidence threshold (40% vs 60%)
- More sensitive to missing headers
- Flags ambiguous cases as bots
You can customize detection in middleware.ts:
const analysis = analyzeRequest(request.headers, {
strictMode: true,
checkUserAgent: true,
checkHeaders: true,
checkIP: true,
confidenceThreshold: 0.5, // Custom threshold
});# Simple curl request (will be detected as bot)
curl http://localhost:3000
# Custom user-agent
curl -H "User-Agent: MyBot/1.0" http://localhost:3000A comprehensive Python script that simulates bot, human, and suspicious traffic:
# Install dependencies
pip install requests
# Run with default settings (100 requests, 60% bot ratio)
python3 testing/bot-simulator.py
# Custom configuration
python3 testing/bot-simulator.py --requests 50 --threads 5 --bot-ratio 0.7
# Test against a different URL
python3 testing/bot-simulator.py --url https://your-app.vercel.app --requests 30Options:
--url: Target URL (default: http://localhost:3000)--requests: Total number of requests (default: 100)--threads: Number of concurrent threads (default: 10)--bot-ratio: Ratio of bot traffic 0.0-1.0 (default: 0.6)
Output includes:
- Real-time request logging with IP, detection status, confidence, and user-agent
- Summary statistics (detection rates, false positives, accuracy)
- JSON results saved to
testing/results/
For performance and load testing with k6:
# Install k6
brew install k6 # macOS
# or visit https://k6.io/docs/getting-started/installation/
# Run the full load test (ramps up to 50 concurrent users)
k6 run testing/k6-load-test.js
# Run a quick test (fewer requests)
k6 run testing/k6-quick-test.jsThe k6 tests simulate mixed traffic (60% bots, 30% humans, 10% suspicious) and track:
- Bot detection rates
- Human pass-through rates
- False positives
- Response times (p95)
- Detection confidence
# Analyze k6 results
node testing/analyze-k6.js
# Analyze bot simulator results (analyzes the latest JSON file)
node testing/analyze-results.jsResults are saved to testing/results/ as JSON files with timestamps.
Visit /debug to see detailed bot detection information:
- User-Agent analysis
- Browser fingerprint
- Cookie values
- Detection factors and scores
The middleware logs bot detections to console. In production, send to your analytics:
// middleware.ts
if (analysis.isBot) {
// Send to your analytics service
await analytics.track('bot_detected', {
path: request.nextUrl.pathname,
confidence: analysis.confidence,
reasons: analysis.reasons,
userAgent: request.headers.get('user-agent'),
});
}- Bot detection rate (% of requests flagged)
- False positive rate (legitimate users blocked)
- MAU before/after implementation
- Confidence score distribution
-
Cookie Security: Bot detection cookie is set with
httpOnly: falseto allow client access, but usessameSite: 'strict'andsecurein production. -
IP Privacy: IP addresses are analyzed but not stored. Consider GDPR/privacy regulations.
-
False Positives: Monitor and tune thresholds to minimize blocking legitimate users.
-
Evasion: Sophisticated bots may bypass detection. Use as part of a broader security strategy.
- Middleware overhead: ~1-5ms per request
- Cookie size: ~150 bytes
- No external API calls: All detection runs locally
- Caching: Bot detection result cached in cookie for 24 hours
- Check browser console for initialization errors
- Visit
/debugto see detection details - Verify environment variables are set
- Lower the confidence threshold in strict mode
- Disable strict mode
- Adjust
confidenceThresholdin middleware - Check if corporate VPN/proxy is affecting headers
- Review detection reasons in logs
- Check middleware is running (add console.log)
- Verify matcher pattern includes your routes
- Check for cookie-blocking browser extensions
- Ensure middleware.ts is in the root directory
.
├── app/
│ ├── layout.tsx # Root layout
│ ├── page.tsx # Home page with bot detection demo
│ ├── debug/
│ │ └── page.tsx # Debug page showing detection details
│ └── globals.css # Global styles
├── lib/
│ ├── bot-detection/
│ │ ├── types.ts # TypeScript types
│ │ ├── user-agent-detector.ts # UA analysis
│ │ ├── header-analyzer.ts # HTTP header analysis
│ │ ├── ip-analyzer.ts # IP address analysis
│ │ ├── detector.ts # Main detection logic
│ │ └── index.ts # Exports
│ └── braze/
│ ├── types.ts # Braze types
│ ├── init.ts # Braze initialization
│ ├── useBraze.ts # React hooks
│ └── index.ts # Exports
├── testing/
│ ├── bot-simulator.py # Python bot/human traffic simulator
│ ├── k6-load-test.js # k6 load testing script
│ ├── k6-quick-test.js # k6 quick test script
│ ├── analyze-k6.js # k6 results analyzer
│ ├── analyze-results.js # Bot simulator results analyzer
│ ├── setup.sh # Testing environment setup
│ └── results/ # Test results (JSON files)
├── middleware.ts # Next.js middleware (bot detection)
├── next.config.js # Next.js configuration
├── tsconfig.json # TypeScript configuration
├── package.json # Dependencies
└── .env.example # Environment variables template
Improvements and suggestions welcome! Key areas:
- Additional detection heuristics
- IP intelligence service integration
- Machine learning models
- Performance optimizations
This is a demonstration project. Use and modify as needed for your use case.