Skip to content

Next.js Integration

Next.js has built-in support for Middleware that runs at the edge before every request. This is the cleanest way to integrate SeoRend — no additional server needed.

Bot → Next.js Middleware (edge) → SeoRend API → HTML response
User → Next.js Middleware (edge) → Your Next.js App

The middleware runs on Vercel’s Edge Network, Cloudflare, or any compatible runtime.

  1. Add SEOREND_API_KEY to your environment:

    .env.local
    SEOREND_API_KEY=sk_live_your_key_here

    For production, add it to your hosting provider’s environment variables (Vercel, Railway, etc.).

  2. Place this file in the root of your project (same level as package.json, not inside src/):

    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_EXT = /\.(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(pattern => ua.includes(pattern));
    }
    export async function middleware(request: NextRequest) {
    const { pathname } = request.nextUrl;
    const ua = request.headers.get('user-agent') ?? '';
    // Skip: non-GET, static assets, Next.js internals, API routes
    if (
    request.method !== 'GET' ||
    STATIC_EXT.test(pathname) ||
    pathname.startsWith('/_next/') ||
    pathname.startsWith('/api/') ||
    pathname.startsWith('/__nextjs') ||
    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] SEOREND_API_KEY is not set');
    return NextResponse.next();
    }
    try {
    const renderResponse = 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 (!renderResponse.ok) {
    // On 4xx/5xx errors, serve normally — site keeps working
    return NextResponse.next();
    }
    const data = await renderResponse.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',
    'X-Seorend-Render-Time': String(data.timings?.total_ms ?? 0),
    },
    });
    } catch (err) {
    console.error('[SeoRend] Render error:', err);
    return NextResponse.next();
    }
    }
    // Apply middleware to all routes except static files
    export const config = {
    matcher: [
    '/((?!_next/static|_next/image|favicon|robots.txt|sitemap).*)',
    ],
    };
  3. Terminal window
    npm run dev

    Then test bot detection:

    Terminal window
    curl -s -I -A "Googlebot/2.1" http://localhost:3000/
    # Should see: X-Seorend-Cache: MISS (or HIT)
    curl -s -I -A "Mozilla/5.0 Chrome/120" http://localhost:3000/
    # Should NOT have X-Seorend-* headers
  4. Terminal window
    # Vercel
    vercel --prod
    # Or push to your connected Git branch
    git push origin main

    Make sure SEOREND_API_KEY is set in your deployment environment.

SeoRend middleware integrates seamlessly with:

  • Static site generation (SSG)
  • Server-side rendering (SSR)
  • Incremental Static Regeneration (ISR)
  • Client-side rendering (CSR) — this is the primary use case for SeoRend

Middleware not running on Vercel: Check that middleware.ts is at the project root, not inside src/. Vercel looks for it at the root level.

process.env.SEOREND_API_KEY is undefined at runtime: Edge Middleware uses the Edge Runtime which does have access to environment variables — make sure the variable is added to Vercel’s Environment Variables settings (not just .env.local).

Infinite loop: The x-seorend-processed header check prevents loops, but if you have a reverse proxy in front, ensure it doesn’t strip custom headers.