Vercel Integration
Vercel’s Edge Middleware runs before your app on Vercel’s CDN, making it the perfect place to intercept bot requests and route them through SeoRend.
How It Works
Section titled “How It Works”Bot → Vercel Edge Middleware → SeoRend API → Rendered HTMLUser → Vercel Edge Middleware → Your App (unchanged)The middleware runs in under 1ms for regular users (just a header check) and only adds latency for bots.
Requirements
Section titled “Requirements”- Vercel project (any framework: Next.js, SvelteKit, Nuxt, Astro, etc.)
- SeoRend API key from app.seorend.com
-
Add Your API Key to Vercel
Section titled “Add Your API Key to Vercel”In your Vercel project:
- Go to Settings → Environment Variables
- Add
SEOREND_API_KEYwith your API key as the value - Set scope to Production (and Preview if needed)
-
Create the Middleware File
Section titled “Create the Middleware File”Create
middleware.tsin the root of your project (next topackage.json):middleware.ts import { NextRequest, NextResponse } from 'next/server';const BOT_PATTERNS = [// Google'googlebot', 'googleother', 'storebot-google', 'google-inspectiontool','google-extended', 'google-cloudvertexbot', 'google-favicon','apis-google', 'adsbot-google', 'mediapartners-google', 'feedfetcher-google', 'google page speed',// Bing / Microsoft'bingbot', 'bingpreview', 'msnbot', 'adidxbot', 'microsoftpreview',// Yandex'yandexbot', 'yandeximages', 'yandexvideo', 'yandexmobilebot', 'yandexmetrika', 'yandexaccessibilitybot', 'yabrowser',// Baidu & other search'baiduspider', 'duckduckbot', 'duckassistbot', 'applebot', 'naver','sogouspider', '360spider', 'coccoc', 'seznambot', 'qwantbot','ecosia', 'yahoo', 'exabot', 'petalbot', 'bravebot',// AI / LLM crawlers'gptbot', 'oai-searchbot', 'chatgpt-user', 'chatgpt','claudebot', 'claude-web', 'claude-user', 'claude-searchbot', 'anthropic-ai','perplexitybot', 'perplexity-user', 'amazonbot', 'ccbot','meta-externalagent', 'meta-externalfetcher','mistralai-user', 'cohere-ai', 'ai2bot','diffbot', 'deepseekbot', 'firecrawlagent', 'kagi-fetcher','bytespider', 'tiktokspider','xai-crawler', 'timpibot', 'anchor browser', 'novellum ai crawl', 'proratainc',// Social media / link previews'facebookexternalhit', 'facebookcatalog', 'facebookbot', 'facebookplatform', 'instagram','twitterbot', 'linkedinbot', 'pinterestbot', 'pinterest','slackbot', 'slack-imgproxy', 'discordbot', 'telegrambot', 'whatsapp','redditbot', 'snapchat', 'vkshare','tumblr', 'flipboard', 'outbrain', 'embedly', 'bitlybot', 'quora link preview', 'line', 'viber',// SEO tools'semrushbot', 'splitsignalbot', 'ahrefsbot', 'ahrefssiteaudit','mj12bot', 'rogerbot', 'dotbot', 'chrome-lighthouse', 'screaming frog','oncrawlbot', 'botifybot', 'deepcrawl', 'lumar', 'dataforseobot', 'serpstatbot', 'w3c_validator',// Internet Archive'ia_archiver', 'archive.org_bot',];const STATIC_EXTENSIONS = /\.(js|css|png|jpg|jpeg|gif|ico|svg|webp|avif|woff2?|ttf|eot|pdf|zip|mp4|mp3|wasm|map)$/i;function isBot(userAgent: string): boolean {const ua = userAgent.toLowerCase();return BOT_PATTERNS.some(p => ua.includes(p));}export async function middleware(request: NextRequest) {const ua = request.headers.get('user-agent') || '';const { pathname } = request.nextUrl;// Skip non-GET, static assets, API routes, and internal Next.js pathsif (request.method !== 'GET' ||STATIC_EXTENSIONS.test(pathname) ||pathname.startsWith('/api/') ||pathname.startsWith('/_next/') ||pathname.startsWith('/__')) {return NextResponse.next();}// Loop guardif (request.headers.get('x-seorend-processed') === '1') {return NextResponse.next();}if (!isBot(ua)) {return NextResponse.next();}const apiKey = process.env.SEOREND_API_KEY;if (!apiKey) {console.error('SEOREND_API_KEY not set');return NextResponse.next();}try {const renderRes = await fetch('https://render.seorend.com/v1/render', {method: 'POST',headers: {'Content-Type': 'application/json','Authorization': `Bearer ${apiKey}`,},body: JSON.stringify({url: request.url,userAgent: ua,}),});if (!renderRes.ok) {// Fallback to origin on any errorreturn NextResponse.next();}const data = await renderRes.json();return new NextResponse(data.html, {status: data.status || 200,headers: {'Content-Type': 'text/html; charset=utf-8','X-Seorend-Cache': data.cache?.hit ? 'HIT' : 'MISS',},});} catch {return NextResponse.next();}}export const config = {matcher: ['/((?!_next/static|_next/image|favicon).*)'],}; -
Deploy
Section titled “Deploy”Terminal window vercel --prodOr push to your Git branch connected to Vercel — it deploys automatically.
Verification
Section titled “Verification”# Should return X-Seorend-Cache headercurl -s -I -A "Googlebot/2.1" https://your-app.vercel.app/
# Should NOT have X-Seorend headerscurl -s -I -A "Mozilla/5.0 Chrome/120" https://your-app.vercel.app/Troubleshooting
Section titled “Troubleshooting”Middleware not running: ensure middleware.ts is in the project root (not inside src/)
Environment variable not found: Edge Middleware does NOT have access to server-side env vars by default — go to Vercel Settings → Environment Variables and confirm SEOREND_API_KEY is set for the correct environment.
413 / body too large: Vercel Edge responses are limited to 4MB. For very large pages this won’t be an issue in practice, but worth knowing.
Next Steps
Section titled “Next Steps”- Testing with curl — verify bot detection is working
- Configure caching — understand cache TTL settings