Skip to main content
Documentation

Nuxt Installation Guide

Last researched 2026-08-26

This is the canonical Virent installation guide for Nuxt applications.

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 Nuxt website.

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

Install two separate things:
1. Human Analytics (browser): Add the Virent browser script through useHead in the root Nuxt app component.
2. AI Crawler Analytics (server): Set server env values and add a Nitro server plugin for completed crawler requests.

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:
- Use useHead in app/app.vue for one global script install.
- Set defer on the script so the Nuxt app is not blocked.
- Keep custom event SDK usage in client-only composables or plugins.

AI Crawler Analytics best practices:
- Use a Nitro server plugin and its afterResponse hook so crawler telemetry includes the final response status.
- Use runtime config or platform environment variables for the secret; never expose it through public runtime config.
- Filter out static assets and API routes unless those routes should be 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 Nuxt page and inspect the head for the Virent script.
- Navigate to another route and confirm pageview traffic.
- Refresh Human Analytics installation status.
- Start Nuxt in a server-capable mode.
- Request a route with curl -A "GPTBot".
- Refresh AI Analytics status in Virent.

Research Notes#

  • Nuxt useHead manages scripts in the document head.
  • Nitro server plugins can subscribe to afterResponse, after the final status is known.
  • Static-only output has no request-time Nitro hook; install crawler tracking at the hosting provider instead.

Primary references:

Human Analytics#

Add the browser script globally in app/app.vue.

TXT
<script setup lang="ts">useHead({  script: [    {      src: "https://app.virent.app/js/script.js",      defer: true,      "data-write-key": "vha_pk_...",    },  ],})</script><template>  <NuxtLayout>    <NuxtPage />  </NuxtLayout></template>

Best practices:

  • Keep the script global in app/app.vue.
  • Use defer so Nuxt hydration is not blocked.
  • Keep custom event code in client-side composables or plugins.
  • Use public runtime config only for publishable values.

Verification:

  • Open a Nuxt route and inspect the head.
  • Navigate to another route and confirm pageview traffic.
  • Refresh Human Analytics status.

Troubleshooting:

  • If the script is absent, confirm the project uses app/app.vue and the useHead call is not conditional.
  • If CSP blocks Virent, allow the origin in script-src and connect-src.
  • If local events are expected, temporarily add data-allow-localhost="true".

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#

Add private env values:

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

Create a Nitro server plugin:

TS
// server/plugins/virent-crawler.tsconst 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 default defineNitroPlugin((nitroApp) => {	nitroApp.hooks.hook("afterResponse", async (event) => {	  const requestUrl = getRequestURL(event)	  const userAgent = getHeader(event, "user-agent") ?? ""	  if (staticAssetPattern.test(requestUrl.pathname) || !aiCrawlerPattern.test(userAgent)) {		return	  }	  await $fetch(process.env.VIRENT_INGEST_ENDPOINT!, {		method: "POST",		headers: { "x-api-key": process.env.VIRENT_INGEST_SECRET! },		body: {		  siteId: process.env.VIRENT_SITE_ID!,		  url: requestUrl.toString(),		  host: requestUrl.host,		  path: requestUrl.pathname,		  method: event.method,		  referer: getHeader(event, "referer") ?? null,		  userAgent,		  statusCode: event.node.res.statusCode,		  requestId: crypto.randomUUID(),		  timestamp: new Date().toISOString(),		},	  })	})})

Best practices:

  • Use the Nitro afterResponse hook so telemetry has the final response status and does not delay route handling.
  • Use private runtime config or server environment variables for the secret.
  • Move tracking to the CDN/edge layer for static-only Nuxt output.

Verification:

SH
curl -A "GPTBot" https://your-domain.com/
  • Confirm the Nitro server plugin runs.
  • Refresh AI Analytics status.

Troubleshooting:

  • If afterResponse does not run, confirm the deployment uses a server/Nitro runtime.
  • If the secret is undefined, check private runtime config and deployment env names.
  • If events are duplicated, ensure only one middleware or edge layer sends events.