Skip to main content
Documentation

Express Installation Guide

Last researched 2026-08-26

This is the canonical Virent installation guide for Express applications.

Coverage limits: crawler middleware observes only requests handled by this Node.js process. Pages served by a CDN or static host in front of Express need provider logs. Consent, Do Not Track (opt-in) and repeated-initialization rules are in Supported runtimes and coverage limits.

Install with your coding agent

Hand the install to Claude Code, Codex, Cursor or another coding agent. It reads this guide, asks for your keys and keeps the server secret out of client code.

Prompt
Install Virent analytics on my Express website.

Read the canonical guide first and follow its code exactly:
https://virent.app/docs/installation/express.md

Install two separate things:
1. Human Analytics (browser): Place the script in the common EJS, Pug, Handlebars, or HTML layout.
2. AI Crawler Analytics (server): Add an Express middleware that posts crawler request metadata after responses finish.

Values I will give you (they are in Virent under Settings > Connection):
- Publishable browser key, starts with vha_pk_. Safe in browser code.
- Site ID (VIRENT_SITE_ID) and server secret (VIRENT_INGEST_SECRET, starts with vha_sk_). Server only.
Ask me for them. Never invent a key or use a masked preview such as vha_sk_••••.

Human Analytics best practices:
- Add the browser script to the shared HTML template or layout rendered by Express.
- Keep the script in the head or at the end of body consistently across all routes.
- Use the npm SDK in browser bundles only for custom events.

AI Crawler Analytics best practices:
- Register the Virent middleware before route handlers so every page request can be observed.
- Send telemetry after the response finishes so the final HTTP status code is accurate.
- Skip static asset middleware paths and health checks.

Rules:
- Keep VIRENT_INGEST_SECRET in server environment variables. Never put it in client code, public env variables (NEXT_PUBLIC_, VITE_, PUBLIC_) or committed files.
- Exclude API routes, static assets, images, favicons, robots.txt and sitemaps from crawler capture.
- If the site sends a Content-Security-Policy, allow https://app.virent.app in script-src and connect-src.
- Do not change unrelated code.

When you finish, list the files you changed and how I verify each path:
- Render a page and view source for the Virent script.
- Open the page in a browser and check Network requests.
- Refresh Human Analytics status.
- Run the Express server.
- Request a page with curl -A "GPTBot".
- Check server logs and refresh Virent status.

Research Notes#

  • Express middleware is registered with app.use() and runs in registration order.
  • Middleware should call next() unless it intentionally ends the request-response cycle.
  • Express has no fixed template system; Human Analytics belongs in the shared template or HTML partial used by the app.

Primary references:

Human Analytics#

Add the script to your shared template.

HTML
<!-- views/layout.ejs, views/partials/head.ejs, or your shared template --><head>  <meta charset="utf-8" />  <script    defer    data-write-key="vha_pk_..."    src="https://app.virent.app/js/script.js"  ></script></head>

Best practices:

  • Add the script to the common layout used by all public pages.
  • If Express serves a React/Vue bundle, edit the static app index.html instead.
  • Use the npm SDK only in browser assets for custom events.
  • Update Helmet or reverse proxy CSP headers if present.

Verification:

  • Render a page and view source for the Virent script.
  • Open the page in a browser and check Network requests.
  • Refresh Human Analytics status.

Troubleshooting:

  • If only some pages track, identify additional layouts.
  • If CSP blocks Virent, update Helmet or proxy headers.
  • If custom event code runs on the server, move it into browser assets.

Ad blockers and privacy tools often block third-party analytics hosts, which silently drops visits. Relay the script and events through your own domain under /_vr, and load the script with data-api="/_vr" so events go to your domain too. The relay must forward the visitor's IP and country in x-virent-forwarded-for and x-virent-forwarded-country, with your website's secret key (vha_sk_…) in x-virent-proxy-key; without the key Virent ignores the forwarded headers and sees your server's network instead of your visitors'. Relay only /_vr/js/script.js and /_vr/v1/*, and never forward cookies or authorization headers. See First-party analytics for how Virent trusts these headers.

With nginx, add these locations to the site's server block:

TXT
location = /_vr/js/script.js {  proxy_pass https://app.virent.app/js/script.js;  proxy_set_header Host app.virent.app;  proxy_ssl_server_name on;  proxy_set_header Cookie "";}location /_vr/v1/ {  proxy_pass https://app.virent.app/v1/;  proxy_set_header Host app.virent.app;  proxy_ssl_server_name on;  proxy_set_header Cookie "";  proxy_set_header Authorization "";  proxy_set_header X-Virent-Forwarded-For $remote_addr;  # Your website's secret key: Virent trusts the forwarded IP only with it.  proxy_set_header X-Virent-Proxy-Key "vha_sk_...";}

On Cloudflare, deploy a Worker on the example.com/_vr/* route that copies only accept-language, content-type, origin and user-agent, sets x-virent-forwarded-for from cf-connecting-ip, x-virent-forwarded-country from request.cf.country and x-virent-proxy-key from a Worker secret holding your website's secret key, and fetches the same path without /_vr from https://app.virent.app. The dashboard install panel has the full Worker.

Then load the script from your domain:

HTML
<script  defer  data-api="/_vr"  data-write-key="vha_pk_..."  src="/_vr/js/script.js"></script>

AI Analytics#

Set server environment values:

ENV
VIRENT_SITE_ID=site_...VIRENT_INGEST_SECRET=vha_sk_...VIRENT_INGEST_ENDPOINT=https://app.virent.app/v1/ingest/bot

Register middleware before page routes.

TS
import type { NextFunction, Request, Response } from "express"const staticAssetPattern = /\.(?:png|jpg|jpeg|gif|svg|ico|webp|css|js|map|txt|xml)$/iconst aiCrawlerPattern = /GPTBot|OAI-SearchBot|ChatGPT-User|ClaudeBot|claudebot|Claude-Web|Claude-User|Claude-SearchBot|PerplexityBot|PerplexityBot\/|Perplexity-User|Perplexity-User\/|Google-CloudVertexBot|DeepSeekBot|DeepSeekSpider|CCBot|Bytespider|meta-externalagent|meta-webindexer|meta-externalfetcher|Amazonbot|DuckAssistBot|YouBot/iexport function virentCrawlerAnalytics(req: Request, res: Response, next: NextFunction) {  res.on("finish", () => {    const userAgent = req.get("user-agent") ?? ""    if (staticAssetPattern.test(req.path) || !aiCrawlerPattern.test(userAgent)) {      return    }    void fetch(process.env.VIRENT_INGEST_ENDPOINT!, {      method: "POST",      headers: {        "Content-Type": "application/json",        "x-api-key": process.env.VIRENT_INGEST_SECRET!,      },      body: JSON.stringify({        siteId: process.env.VIRENT_SITE_ID!,        url: `${req.protocol}://${req.get("host")}${req.originalUrl}`,        host: req.get("host"),        path: req.path,        method: req.method,        referer: req.get("referer") ?? null,        userAgent,        statusCode: res.statusCode,        requestId: crypto.randomUUID(),        timestamp: new Date().toISOString(),      }),    }).catch((error) => {      console.error("Virent crawler analytics failed", error)    })  })  next()}app.use(virentCrawlerAnalytics)

Best practices:

  • Register before route handlers.
  • Send after finish so the final response status is available.
  • Skip static assets and health checks.
  • Node 18+ includes fetch; older runtimes need an HTTP client.

Verification:

SH
curl -A "GPTBot" https://your-domain.com/
  • Confirm Express logs the request and posts to Virent.
  • Refresh AI Analytics status.

Troubleshooting:

  • If no events arrive, confirm app.use(virentCrawlerAnalytics) runs before routes.
  • If static files are counted, add extension exclusions.
  • If fetch is undefined, upgrade Node or use the app's existing HTTP client.