Skip to main content
Documentation

Next.js Installation Guide

Last researched 2026-08-26

This is the canonical Virent installation guide for Next.js projects. It covers Human Analytics browser tracking and AI Analytics server-side crawler tracking.

Coverage limits: proxy.ts runs on Node.js before rendering, so the final response status is unknown, and requests answered from a CDN cache never reach it. Import @virent.app/sdk/next only from server files; from SDK 0.4.0 a client bundle that imports it fails to load. Edge isolates need provider logs for crawler coverage. See 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 Next.js website.

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

Install two separate things:
1. Human Analytics (browser): Add the Virent browser script through next/script in the root App Router layout.
2. AI Crawler Analytics (server): Install the SDK helper, set server-only environment values, and add a root proxy.ts file.

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:
- Install the browser script once in app/layout.tsx so it loads across all App Router routes.
- Use next/script instead of a raw script tag when working inside a Next.js layout.
- Keep the publishable key in the data-write-key attribute; it is safe for the browser, unlike the crawler secret.

AI Crawler Analytics best practices:
- Use proxy.ts for Next.js 16+ projects and keep the matcher narrow enough to exclude API routes, static assets, images, robots.txt, sitemaps, and common files.
- Store the crawler secret in server environment variables only. Never expose it through NEXT_PUBLIC_ variables or client bundles.
- Capture crawler requests before rendering so bots that never execute JavaScript are still measured.

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:
- Open a production or preview page and check Network for /js/script.js.
- Confirm a pageview request is sent after navigation.
- Refresh Virent installation status and confirm Human Analytics changes to receiving events.
- Run the app and request a page with curl -A "GPTBot".
- Refresh Virent installation status and confirm AI Analytics changes to receiving crawler events.
- Inspect server logs for failed outbound requests to the Virent ingest endpoint.

Research Notes#

  • Next.js 16 App Router supports global third-party scripts through next/script in layouts.
  • Next.js 16 renamed the middleware.ts file convention to proxy.ts. New AI Analytics installs should use proxy.ts.
  • Proxy runs before routes render and should use a matcher that excludes static assets and framework internals.

Primary references:

Human Analytics#

Install Human Analytics in app/layout.tsx so the browser script loads on every App Router route.

TSX
import Script from "next/script"export default function RootLayout({ children }) {  return (    <html lang="en">	  <body>		{children}		<Script		  src="https://app.virent.app/js/script.js"		  data-write-key="vha_pk_..."		  strategy="afterInteractive"		/>	  </body>    </html>  )}

Use the npm SDK only when custom product events, identities, or goals are needed.

SH
pnpm add @virent.app/sdk@^0.3.0
TS
import { identify, initVirent, pageview, trackEvent, trackGoal } from "@virent.app/sdk/browser"initVirent({ writeKey: "vha_pk_..." })await pageview({ path: "/pricing" })await trackEvent({ name: "signup_started", properties: { plan: "pro" } })await identify({ userId: "customer_123", traits: { plan: "pro" } })await trackGoal({ name: "signup_completed", idempotencyKey: "order_123" })

Best practices:

  • Use next/script instead of a raw script tag in React layouts.
  • Keep strategy="afterInteractive" for normal analytics so hydration is not blocked.
  • Keep the publishable key in data-write-key; do not use the server secret in client code.
  • If Content Security Policy is enabled, allow the Virent origin in script-src and connect-src.

Verification:

  • Open a production or preview page and verify /js/script.js loads.
  • Navigate between App Router routes and confirm pageview requests are sent.
  • Refresh Virent installation status and confirm Human Analytics is receiving events.

Troubleshooting:

  • If the script is absent, confirm it is in the root layout used by the route.
  • If the browser blocks the script, update CSP in next.config.js, vercel.json, proxy.ts, or the hosting platform.
  • Localhost traffic is skipped by default. Add data-allow-localhost="true" only for intentional local testing.

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.

A rewrites() entry in next.config.js cannot add request headers, so the relay is a rewrite in proxy.ts. This file replaces the crawler-only proxy.ts from AI Analytics below.

TS
import { createVirentBotProxy } from "@virent.app/sdk/next"import { type NextFetchEvent, type NextRequest, NextResponse } from "next/server"const virentOrigin = "https://app.virent.app"const relayedHeaders = ["accept-language", "content-type", "origin", "user-agent"]const trackBots = createVirentBotProxy({  siteId: process.env.VIRENT_SITE_ID!,  writeKey: process.env.VIRENT_INGEST_SECRET!,  endpoint: process.env.VIRENT_INGEST_ENDPOINT || "https://app.virent.app/v1/ingest/bot",  trustedProxy: "vercel",})export function proxy(request: NextRequest, event: NextFetchEvent) {  const { pathname, search } = request.nextUrl  if (pathname.startsWith("/_vr/")) {    const headers = new Headers()    for (const name of relayedHeaders) {      const value = request.headers.get(name)      if (value) headers.set(name, value)    }    const ip = request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()    const country = request.headers.get("x-vercel-ip-country")    if (ip) headers.set("x-virent-forwarded-for", ip)    if (country) headers.set("x-virent-forwarded-country", country)    // The secret key lets Virent trust the forwarded IP and country.    headers.set("x-virent-proxy-key", process.env.VIRENT_INGEST_SECRET!)    return NextResponse.rewrite(      new URL(pathname.slice("/_vr".length) + search, virentOrigin),      { request: { headers } }    )  }  trackBots(request, event)}export const config = {  matcher: [    "/_vr/:path*",    "/((?!api|_next/static|_next/image|favicon.ico|robots.txt|sitemap.xml|.*\\.(?:png|jpg|jpeg|gif|svg|ico|webp|css|js|map)$).*)",  ],}

Then load the script from your domain:

TSX
<Script  src="/_vr/js/script.js"  data-api="/_vr"  data-write-key="vha_pk_..."  strategy="afterInteractive"/>

With the browser SDK, pass endpoint: "/_vr/v1/ingest/batch", identifyEndpoint: "/_vr/v1/identify" and goalEndpoint: "/_vr/v1/track/goal" to initVirent. The SDK route createVirentAnalyticsProxy (secret key, /vt/ paths) remains an alternative.

AI Analytics#

Install AI Analytics in proxy.ts at the project root or src/proxy.ts if the app uses a src directory.

SH
pnpm add @virent.app/sdk@^0.3.0
ENV
VIRENT_SITE_ID=site_...VIRENT_INGEST_SECRET=vha_sk_...VIRENT_INGEST_ENDPOINT=https://app.virent.app/v1/ingest/bot
TS
// proxy.tsimport { createVirentBotProxy } from "@virent.app/sdk/next"export const proxy = createVirentBotProxy({  siteId: "site_...",  writeKey: process.env.VIRENT_INGEST_SECRET!,  trustedProxy: "vercel",})export const config = {  matcher: [    "/((?!api|_next/static|_next/image|favicon.ico|robots.txt|sitemap.xml|.*\\.(?:png|jpg|jpeg|gif|svg|ico|webp|css|js|map)$).*)",  ],}

Best practices:

  • Use proxy.ts for Next.js 16+ rather than creating new middleware.ts code.
  • Keep the matcher exclusions for API routes, static assets, image optimization, favicons, robots, sitemaps, and common file extensions.
  • Store VIRENT_INGEST_SECRET only in server environment variables.
  • Use trustedProxy: "vercel" on Vercel so the SDK can hash the real client IP without trusting arbitrary forwarding headers elsewhere.
  • Do not depend on React component state or mutable globals from Proxy.

Verification:

SH
curl -A "GPTBot" https://your-domain.com/
  • Confirm the request reaches proxy.ts.
  • Confirm Virent receives crawler events.
  • Refresh Virent installation status and confirm AI Analytics is receiving crawler events.

Troubleshooting:

  • If every static asset creates events, tighten the matcher exclusions.
  • If no events arrive, confirm proxy.ts is at the same level as app or pages.
  • If the secret is masked in the UI, rotate and reveal the key before installing.