Skip to content
Ranqo
Docs
DashboardGet started
GuidesMethodologyIntegrations
Video tutorials

Overview

  • Integrations

Analytics

  • Google Analytics 4

Publishing

  • WordPress

Site Tracking

  • Site Tracking
  • Next.js
  • Express
  • Cloudflare Worker
  • Any backend
  • Verify
  • Site keys
  • Intake API
  • Privacy

3 sections

Site Tracking

Site Tracking for Express

View as MarkdownOpen this page as .md
ChatGPTOpen in ChatGPTAsk about this pageClaudeOpen in ClaudeAsk about this page

Send AI crawler visits and AI referrals from an Express app to Ranqo with one middleware function, with notes for Fastify, Hono and Koa.

On an Express app, Site Tracking is one middleware function. It reports each page request to Ranqo after your response has gone out, so your visitors never wait on it.

  1. Copy your site key

    On the Traffic page, press Set up Site Tracking, then Generate site key on the Install Site Tracking page. Copy the key. It starts with rk_live_.

  2. Add the key to your environment

    .env
    RANQO_SITE_KEY=rk_live_YOUR_SITE_KEY

    Set the same variable in your hosting provider's environment for every environment you want tracked. Keep it out of your repository.

  3. Add the middleware file

    Save this as ranqo-intake.ts (or .js, without the type imports). It uses the global fetch, available from Node.js 18.

    ranqo-intake.ts
    // ranqo-intake.ts — wire with: app.use(ranqoIntake);
    import type { Request, Response, NextFunction } from 'express';
    
    const SKIP_EXT = /\.(png|jpg|jpeg|svg|gif|webp|ico|css|js|mjs|map|woff2?|ttf|otf|eot|txt|xml|json)$/i;
    const TRACK_AI = /^\/(robots\.txt|llms(-full)?\.txt|sitemap(-.*)?\.xml|ai\.txt|\.well-known\/.*)$/;
    
    // OPTIONAL: skip self-tracking on subdomains you don't want counted as
    // "marketing site visitors" (e.g. your authenticated dashboard).
    const SKIP_HOSTS = new Set<string>([
      // 'app.yoursite.com',
      // 'admin.yoursite.com',
    ]);
    
    export function ranqoIntake(req: Request, res: Response, next: NextFunction) {
      // Forward immediately — fire-and-forget happens after the handler returns.
      next();
    
      res.on('finish', () => {
        // Only track GET. POST/PUT/DELETE/HEAD/OPTIONS never represent a page view.
        if (req.method !== 'GET') return;
    
        // Skip prefetch / RSC requests (matters when Express is proxying or
        // hosting a Next.js app — every <Link> hover would otherwise count).
        const secFetchDest = req.get('sec-fetch-dest');
        if (secFetchDest && secFetchDest !== 'document') return;
        if (req.get('next-router-prefetch')) return;
        if (req.get('next-router-segment-prefetch')) return;
        if (req.get('rsc') === '1') return;
        const secPurpose = req.get('sec-purpose');
        if (secPurpose && secPurpose.startsWith('prefetch')) return;
        if (req.get('purpose') === 'prefetch') return;
    
        const path = req.path;
        if (path.startsWith('/api/')) return;
        if (SKIP_EXT.test(path) && !TRACK_AI.test(path)) return;
        if (SKIP_HOSTS.has(req.get('host') || '')) return;
    
        const params = new URLSearchParams({
          url: `${req.protocol}://${req.get('host')}${req.originalUrl}`,
          userAgent: req.get('user-agent') || '',
          ref: req.get('referer') || '',
          ip: ((req.headers['x-forwarded-for'] as string)?.split(',')[0]?.trim()) || req.ip || '',
          websiteKey: process.env.RANQO_SITE_KEY || '',
        });
    
        fetch(`https://app.ranqo.ai/api/v1/intake/pageview?${params}`).catch(() => {});
      });
    }
  4. Register it before your routes

    Import it and call app.use(ranqoIntake) after your trust proxy setting and before your routes and any express.static middleware. Express runs middleware in order, and a route or static handler that sends a response does not pass the request on, so middleware registered after it never sees that request.

    app.ts
    import express from "express";
    import { ranqoIntake } from "./ranqo-intake";
    
    const app = express();
    app.set("trust proxy", true); // match your proxy setup
    app.use(ranqoIntake);
    app.use(express.static("public"));
    // ...your routes
  5. Deploy and verify

    Deploy, then follow Verify your install. The install page in the dashboard watches for your first real visit and confirms it when it arrives.

What the middleware does#

  • Adds no latency. It calls next() straight away and sends the report to Ranqo when the response has finished, without waiting for Ranqo's answer. A failed report is ignored.
  • Counts page views only. It skips anything but GET, paths under /api/, and the prefetch and framework data requests a Next.js front end makes when it is served through Express.
  • Keeps the files AI crawlers read first. robots.txt, llms.txt, sitemap.xml and /.well-known/* are reported even though other files with those extensions are skipped as static assets.
  • Sends the real visitor address. It reads the first address in x-forwarded-for, which your proxy or load balancer sets, and falls back to req.ip. Ranqo uses the address to check whether a crawler is genuine, so a proxy's own address makes real crawlers look unverified.
Note: Dashboard on the same app?

If the same Express app also serves your signed-in product on another host, add that host to SKIP_HOSTS at the top of the file so your team's own clicks are not counted as site visitors.

Fastify, Hono and Koa#

The same logic works in any Node framework: after the response is sent, apply the same checks, build the same query string and call the intake endpoint without awaiting it.

  • Fastify: put the body of the res.on('finish', ...) handler in an onResponse hook, reading headers from request.headers.
  • Hono and Koa: in app.use(async (c, next) => { ... }) (Koa: (ctx, next)), call await next() first, then run the checks and fire the request.

On a serverless or edge runtime, a request still in flight when the handler returns can be cut off. Use the platform's way of keeping work alive after the response, as the Cloudflare Worker install does with waitUntil.

Next steps#

Verify your install

Send a test event and confirm Ranqo accepted it.

How Ranqo classifies visits

Crawlers, assistants and human referrals, and how each is told apart.
Previous page: Next.jsNext page: Cloudflare Worker
On this page
Ranqo
Docs
Dashboardranqo.aiPrivacyTerms
Be the Source AI Cites.
Ranqo
Docs
GuidesMethodologyIntegrations

Overview

  • Integrations

Analytics

  • Google Analytics 4

Publishing

  • WordPress

Site Tracking

  • Site Tracking
  • Next.js
  • Express
  • Cloudflare Worker
  • Any backend
  • Verify
  • Site keys
  • Intake API
  • Privacy
Get started