Published on 2025-07-29T00:56:00.486Z

AI, LLM, agent, bot analytics with Next.js and PlainSignal

Integrate PlainSignal with your Next.js applications to unlock powerful AI and bot analytics. Our middleware solution makes it simple to add AI SEO tracking capabilities with minimal configuration. By leveraging Next.js's built-in middleware, you can track bot interactions across your entire site without needing to modify individual pages. This solution supports both the App Router and Pages Router, providing complete coverage for your Next.js application.

Features

  • Seamless integration with Next.js middleware
  • Automatic tracking of all routes
  • Real-time AI visibility analytics
  • Easy installation with npm/yarn/pnpm
  • Compatible with app router and pages router
  • Zero impact on site performance (no scripts, backend solution)
  • Tracks all major AI systems including ChatGPT, Claude, Google, Amazon Nova, Bing, and Perplexity

System requirements

  • Next.js > v12

Next.js <> PlainSignal analytics integration

Step by Step Guide To Track AI, LLM, Agent, Bot Traffics using Next.js

  • Step#1: Get your DomainID and DomainApiKey from PlainSignal

    If you already added your website to PlainSignal for tracking, then follow the sub-steps below (Learn how to create your first website analytics in PlainSignal if you haven't done before):

    • Go to https://app.plainsignal.com/s/<your domain>/settings
    • In the Configuration panel you will see your DomainID
    • In the same Configuration panel, to get your DomainApiKey visible click on the ***** link

    At the end of this step you should have 2 values:

    • DomainID
    • DomainApiKey
  • Step#2: Add tracker to Next.js App

    In 2 steps, the integration will be ready to collect all major AI, LLM, agent, bot traffic analytics

    • Step#2.1: Create bot-tracker.js

      Create a new file (e.g., lib/bot-tracker.js) with the following code:

      // Bot regext patterns
      const plainSignalBotPatterns = /(GPTBot|ChatGPT-User|OAI-SearchBot|Amazonbot|Applebot-Extended|Applebot|archive\.org_bot|adidxbot|MicrosoftPreview|bingbot|Bytespider|ClaudeBot|Claude-User|Claude-SearchBot|DuckAssistBot|DuckDuckBot|Googlebot-Image|Googlebot-Video|Googlebot-News|Googlebot|Storebot-Google|Google-InspectionTool|GoogleOther-Image|GoogleOther-Video|GoogleOther|GoogleCloud-VertexBot|Google-Extended|APIs-Google|AdsBot-Google-Mobile|AdsBot-Google|Mediapartners-Google|Google-Safety|FeedFetcher-Google|GoogleProducer|Google-Read-Aloud|Google-Site-Verification|facebookexternalhit|facebookcatalog|meta-externalagent|meta-externalfetcher|MistralAI-User|PerplexityBot|Perplexity-User|PetalBot|ProRateInc|Timpibot|YandexBot|CCBot|LinkedInBot|Yahoo!\sSlurp)/i
      
      // PlainSignal domain configuration
      // Replace {YourDomainID} and {YourDomainApiKey} with your actual values from the first step
      const domainID = '{YourDomainID}';
      const domainApiKey = '{YourDomainApiKey}'
      const plainSignalAPIEndpoint = 'https://eu.plainsignal.com/s/' + domainID + '/19'
      
      // Function to track bot, agent, LLM, AI traffic
      export function trackBotTraffic(req) {
        const userAgent = req.headers.get('user-agent') || '';
      
        if (plainSignalBotPatterns.test(userAgent)) {
          const r = new URL(req.nextUrl.toString())
          const referrer = req.headers.get('referer') // NOTE: It is not a typo!
          let rd = ''
          let rp = ''
          if (referrer !== null && referrer !== undefined && referrer !== '') {
            const ref = new URL(referrer)
            rd = ref.hostname
            rp = ref.pathname
          }
          const p = r.searchParams
          const payload = {
            b: {
              d: {
                a: userAgent
              },
              l: {},
              s: {
                us: p.get('utm_source') || p.get('source'),
                um: p.get('utm_medium'),
                uc: p.get('utm_campaign'),
                ut: p.get('utm_term'),
                uo: p.get('utm_content'),
                ur: p.get('ref'),
                rd: rd,
                rp: rp
              },
              p: {
                p: r.pathname,
              },
              ci: (req.ip || req.headers.get('x-forwarded-for') || '') + userAgent + (req.headers.get('sec-ch-ua') || '')
            }
          }
      
          fetch(plainSignalAPIEndpoint, {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json',
              'x-api-key': domainApiKey
            },
            body: JSON.stringify(payload),
          }).catch(error => {
            console.error('Error calling PlainSignal tracker:', error);
          });
        }
      }
    • Step#2.1: Add the tracker as middleware

      Add the following code to middleware.ts (middleware.js if using js):

      // Add this to your middleware.ts
      import { NextResponse } from 'next/server';
      
      export function middleware(req) {
          trackBotTraffic(req);
      
          // Your other middleware logic...
          return NextResponse.next();
      }

Other integrations