How to Block Bot Traffic in Next.js With No Extra Infra
Three patterns for bot blocking in Next.js — middleware, instrumentation, or preload — ranked by what works on Vercel, self-host, and the App Router.
How to Block Bot Traffic in Next.js With No Extra Infra
Next.js gives you three places to block bots, depending on your deployment target and runtime preference: Edge middleware, server instrumentation, or a Node preload. Each has tradeoffs. Here's when to use which.
For broader context on the firewall layer, see the SecureNow Firewall page.
Option 1: Edge middleware (works on Vercel, simplest)
middleware.ts runs before every matched request, in the Edge Runtime. Use it for IP-based blocking:
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
const BLOCKED_IPS = new Set([
'185.220.101.42',
'185.220.102.245',
]);
const BAD_UA = /sqlmap|nikto|acunetix|masscan|nmap|python-requests/i;
const GOOD_BOTS = /googlebot|bingbot|gptbot|claudebot|perplexitybot/i;
export function middleware(request: NextRequest) {
const ip = request.headers.get('x-forwarded-for')?.split(',')[0]?.trim()
|| request.headers.get('x-real-ip')
|| '';
const ua = request.headers.get('user-agent') || '';
if (GOOD_BOTS.test(ua)) return NextResponse.next();
if (BLOCKED_IPS.has(ip)) return new NextResponse('Forbidden', { status: 403 });
if (BAD_UA.test(ua)) return new NextResponse('Forbidden', { status: 403 });
return NextResponse.next();
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};
The matcher skips static assets so you're not invoking edge middleware for every CSS file (matters on Vercel pricing).
This works on Vercel, Cloudflare Workers (via Open Next), and self-hosted Node. Limit: the static blocklist is whatever you hard-code. To stay current with attack traffic you'd need a refresh mechanism — which Edge Runtime restricts.
Option 2: Larger blocklist via Edge KV / fetch
For a refreshing blocklist on Vercel:
import { NextResponse } from 'next/server';
import { kv } from '@vercel/kv';
export async function middleware(request: NextRequest) {
const ip = request.headers.get('x-forwarded-for')?.split(',')[0]?.trim();
const isBlocked = await kv.sismember('blocklist:ips', ip);
if (isBlocked) return new NextResponse('Forbidden', { status: 403 });
return NextResponse.next();
}
Run a cron (Vercel Cron or external) to update blocklist:ips daily from threat-intel feeds. Workable but you're now maintaining the feed pipeline yourself.
Option 3: Node preload (self-hosted, full IP firewall)
If you self-host Next.js (Railway, Render, AWS, your own server), the simplest option is the Node-runtime preload:
npm install securenow
# Update your start script:
node -r securenow/firewall-only node_modules/next/dist/bin/next start
Or via NODE_OPTIONS:
NODE_OPTIONS='-r securenow/firewall-only' npm start
This puts the firewall at the HTTP-server layer below Next.js. Requests from blocked IPs are rejected before Next.js sees them. The blocklist is a known-bad IP feed refreshed hourly, plus your own blocks. No middleware to maintain.
This is the lowest-effort production setup. The catch: it requires Node runtime, so it doesn't work on Vercel (Edge functions only). For Vercel deployments, stick with Option 1 or 2.
Option 4: Instrumentation (either router, broader hook)
Next.js 14+ supports instrumentation.ts at the project root — a hook that runs once at server startup. Use it to initialize observability and security tools that need to be set up before any request handlers:
// instrumentation.ts (npx securenow init writes it and updates next.config)
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
try {
const securenowNext = await import('securenow/nextjs');
const registerSecureNow = securenowNext.registerSecureNow || securenowNext.default?.registerSecureNow;
registerSecureNow({ captureBody: true });
await import('securenow/nextjs-auto-capture');
} catch (error) {
console.error('[securenow] SecureNow did not start; the app is running without it:', error);
}
}
}
This gives you full SecureNow — firewall plus tracing plus AI investigation — on a self-hosted Node.js runtime. The check on NEXT_RUNTIME ensures you only initialize Node-specific code on the Node runtime; Edge runtime falls through to whatever middleware you have.
What to actually deploy
For most Next.js apps:
- Self-hosted Next.js (Railway, Render, AWS, etc.): Option 3 (
-r securenow/firewall-only) or Option 4 (instrumentation.ts). Maximum protection, zero maintenance. - Vercel: Option 1 (Edge middleware) for IP blocking based on a hard-coded list, plus Option 4 for traces from the Node-runtime portions (not yet tested end to end on Vercel, and the firewall isn't verified on Vercel serverless functions).
- Cloudflare Pages / Open Next: Option 1 with KV-backed blocklist.
A useful matcher config
The default matcher catches everything. For better performance, exclude paths Next.js handles internally:
export const config = {
matcher: [
'/((?!api/health|_next/static|_next/image|favicon.ico|robots.txt|sitemap.xml).*)',
],
};
This skips health checks, static assets, and SEO files from middleware — saves edge function invocations on Vercel and reduces unnecessary compute on self-host.
Verifying it works
For Edge middleware:
curl -i https://yourapp.com/ -H "User-Agent: sqlmap/1.5.7"
# expects: HTTP/1.1 403 Forbidden
For the SecureNow firewall:
npx securenow firewall status
# Shows count of currently blocked IPs and last refresh time
Related
Frequently Asked Questions
Does Next.js middleware run on Vercel Edge?
Yes. middleware.ts runs at the edge in Edge Runtime — limited Node API surface, but full access to request headers and the ability to return Response objects. IP blocking works fine at this layer.
Does middleware run on every request?
Yes by default. You can scope it to specific paths via the matcher config to skip static assets and reduce edge function invocations on Vercel pricing.
Can I use the SecureNow firewall on Vercel?
The HTTP-layer firewall (preload `securenow/firewall-only`) requires Node.js runtime. On Vercel, use Edge Runtime middleware for blocking, or deploy your Next.js app on a Node-runtime host (Railway, Render, AWS) for full firewall support.
Does the App Router change anything?
Not for middleware — `middleware.ts` works identically in both Pages Router and App Router. Instrumentation also works with both routers and offers a different hook point.
Recommended reading
Block one IP, a CIDR range, or a single route and method in a Node.js app within seconds, with no code change: setup, scoping, CLI, MCP, verification and undo.
Oct 27Built your app with a coding agent? 11 warning signs it needs a security audit, each checkable in five minutes, and how to run the full audit yourself.
Oct 26Part 1 of the AI security audit series: set up an AI coding agent to audit your app locally, connect SecureNow, and run your first threat-model pass.
Jul 24