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.
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
useHeadmanages 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:
- https://nuxt.com/docs/4.x/api/composables/use-head
- https://nuxt.com/docs/4.x/directory-structure/server
Human Analytics#
Add the browser script globally in app/app.vue.
<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
deferso 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.vueand theuseHeadcall is not conditional. - If CSP blocks Virent, allow the origin in
script-srcandconnect-src. - If local events are expected, 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 for how Virent trusts these headers.
With nginx, add these locations to the site's server block:
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:
<script defer data-api="/_vr" data-write-key="vha_pk_..." src="/_vr/js/script.js"></script>AI Analytics#
Add private env values:
VIRENT_SITE_ID=site_...VIRENT_INGEST_SECRET=vha_sk_...VIRENT_INGEST_ENDPOINT=https://app.virent.app/v1/ingest/botCreate a Nitro server plugin:
// 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
afterResponsehook 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:
curl -A "GPTBot" https://your-domain.com/- Confirm the Nitro server plugin runs.
- Refresh AI Analytics status.
Troubleshooting:
- If
afterResponsedoes 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.