# React Installation Guide

> Install Virent Human Analytics and AI Crawler Analytics on React.

Canonical: https://stage.virent.app/docs/installation/react
Last updated: 2026-08-26

This is the canonical Virent installation guide for React single-page applications, especially Vite-based React apps.

### Install with your coding agent

Paste this prompt into Claude Code, Codex, Cursor or another coding agent.

```text
Install Virent analytics on my React website.

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

Install two separate things:
1. Human Analytics (browser): Install the pageview script in the Vite index.html head and optionally add the browser SDK for events.
2. AI Crawler Analytics (server): Add server-side capture to the host or backend in front of the React static build.

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 index.html because Vite treats index.html as the app entry point.
- Use a literal publishable key in the script tag or a VITE_ public variable for SDK event code.
- Initialize the npm SDK only once if you also send custom product events.

AI Crawler Analytics best practices:
- Install AI Analytics at the edge, CDN, reverse proxy, or backend that serves the React app because crawler requests do not execute browser JavaScript.
- Keep the server secret in the edge/server runtime environment, not in VITE_ variables.
- Exclude static assets from crawler capture to avoid noisy events.

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:
- Run a production preview and inspect the rendered head for the Virent script.
- Navigate between routes and confirm pageview requests are sent.
- Refresh Virent installation status after visiting the site.
- Request the deployed app with curl -A "GPTBot" https://your-domain.com/.
- Confirm the edge/backend logs one ingest attempt for the HTML route.
- Refresh Virent installation status and confirm AI Analytics receives crawler events.
```

## Research Notes

- Vite treats `index.html` as source code and as the entry point for React applications.
- React SPA code runs in the browser, so it cannot capture AI crawler requests that never execute JavaScript.
- AI Analytics must be installed in the CDN, edge function, reverse proxy, or backend that serves the React build.

Primary references:

- https://vite.dev/guide/
- https://vite.dev/guide/env-and-mode.html

## Human Analytics

Add the Virent browser script to the root `index.html` file.

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <script
      defer
      data-write-key="vha_pk_..."
      src="https://app.virent.app/js/script.js"
    ></script>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>
```

Use the browser SDK for custom events.

```sh
pnpm add @virent.app/sdk
```

```ts
import { initVirent, trackEvent } from "@virent.app/sdk/browser"

initVirent({ writeKey: import.meta.env.VITE_VIRENT_WRITE_KEY })

await trackEvent({
  name: "signup_started",
  properties: { plan: "pro" },
})
```

Best practices:

- Put the script in `index.html`, not inside a React component.
- Use `VITE_` variables only for publishable browser values.
- Keep custom tracking helpers in one small module and import them from components that need events.
- Test React Router navigation after installation.

Verification:

- Run a production build and preview it.
- Inspect the rendered HTML for the Virent script.
- Navigate across client routes and confirm pageview requests.
- Refresh Virent installation status.

Troubleshooting:

- If the script is missing, confirm the root `index.html` was edited.
- If CSP blocks the script, allow the Virent origin in `script-src` and `connect-src`.
- If local test traffic is needed, temporarily add `data-allow-localhost="true"`.

### First-party (recommended)

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](/docs/installation/first-party) for how Virent trusts these headers.

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

```text
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

React itself is not the AI Analytics install point. Install crawler capture in the layer that serves the static React app.

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

Generic edge/server example:

```ts
const staticAssetPattern = /\.(?:png|jpg|jpeg|gif|svg|ico|webp|css|js|map|txt|xml)$/i
const 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/i

export async function trackVirentCrawler(
  request: Request,
  response: Response,
  waitUntil?: (promise: Promise<unknown>) => void
) {
  const url = new URL(request.url)
  const userAgent = request.headers.get("user-agent") ?? ""

  if (staticAssetPattern.test(url.pathname) || !aiCrawlerPattern.test(userAgent)) {
    return
  }

  const report = 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: url.toString(),
      host: url.host,
      path: url.pathname,
      method: request.method,
      referer: request.headers.get("referer"),
      userAgent,
      statusCode: response.status,
      requestId: crypto.randomUUID(),
      timestamp: new Date().toISOString(),
    }),
  })

  if (waitUntil) {
    waitUntil(report.catch(() => {}))
    return
  }

  await report
}
```

Best practices:

- Prefer the provider-native guide in Virent's Connect your site dialog. If your host requires custom code, call this helper after it produces a `Response` and pass its `waitUntil` callback when available.
- Do not expose `VIRENT_INGEST_SECRET` through `VITE_` variables.
- Exclude static assets to avoid noisy crawler events.

Verification:

```sh
curl -A "GPTBot" https://your-domain.com/
```

- Confirm the edge/backend layer posts to Virent once for the HTML route.
- Refresh AI Analytics installation status.

Troubleshooting:

- If no crawler events arrive, confirm the request actually reaches the server layer where tracking was installed.
- If the secret appears in built assets, rotate the key and remove it from public env variables.
- If asset events are counted, add extension exclusions.
