Astro Installation Guide
Last researched 2026-08-26
This is the canonical Virent installation guide for Astro sites and 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 Astro website. Read the canonical guide first and follow its code exactly: https://virent.app/docs/installation/astro.md Install two separate things: 1. Human Analytics (browser): Place the Virent script in the base Astro layout that wraps every page. 2. AI Crawler Analytics (server): Add src/middleware.ts in SSR/on-demand Astro deployments or install the same REST call at the edge for static hosting. 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 once in the shared Astro layout head. - Use plain external script markup for the hosted Virent script; do not bundle it into an island. - Keep event SDK code in client-side components only when custom events are needed. AI Crawler Analytics best practices: - Use Astro middleware only when the deployment has SSR or on-demand rendering. - Keep the crawler secret in the server runtime and never ship it to client islands. - Call next() so Astro can continue rendering the requested page. 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: - Build and preview the Astro site. - Inspect the page head and Network requests. - Refresh Human Analytics status. - Deploy with an SSR-capable adapter or edge layer. - Request a page with curl -A "GPTBot". - Refresh AI Analytics status.
Research Notes#
- Astro layouts are the preferred global HTML location for browser scripts.
- Astro middleware can intercept requests and responses for SSR or on-demand rendering.
- Fully static Astro output has no request-time middleware; AI Analytics must run at the hosting edge or reverse proxy in that case.
Primary references:
- https://docs.astro.build/en/guides/client-side-scripts/
- https://docs.astro.build/en/guides/middleware/
Human Analytics#
Add the Virent browser script to the shared layout that wraps all public pages.
---// src/layouts/BaseLayout.astro---<html lang="en"> <head> <script is:inline defer data-write-key="vha_pk_..." src="https://app.virent.app/js/script.js" ></script> </head> <body> <slot /> </body></html>Best practices:
- Use plain external script markup for the hosted Virent script.
- Do not wrap pageview tracking in a hydrated island.
- Confirm every public route uses the layout.
- Use client-side code only for optional custom event tracking.
Verification:
- Build and preview the Astro site.
- Inspect the rendered head and Network requests.
- Refresh Human Analytics status.
Troubleshooting:
- If the script is absent, confirm all routes use the edited layout.
- If CSP blocks the script, allow Virent in
script-srcandconnect-src. - If custom events do not fire, confirm event code runs in a client-side script or hydrated component.
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#
For SSR or on-demand Astro deployments, use src/middleware.ts.
VIRENT_SITE_ID=site_...VIRENT_INGEST_SECRET=vha_sk_...VIRENT_INGEST_ENDPOINT=https://app.virent.app/v1/ingest/bot// src/middleware.tsimport type { MiddlewareHandler } from "astro"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 const onRequest: MiddlewareHandler = async (context, next) => { const response = await next() const userAgent = context.request.headers.get("user-agent") ?? "" if (!staticAssetPattern.test(context.url.pathname) && aiCrawlerPattern.test(userAgent)) { await fetch(import.meta.env.VIRENT_INGEST_ENDPOINT, { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": import.meta.env.VIRENT_INGEST_SECRET, }, body: JSON.stringify({ siteId: import.meta.env.VIRENT_SITE_ID, url: context.url.toString(), host: context.url.host, path: context.url.pathname, method: context.request.method, referer: context.request.headers.get("referer"), userAgent, statusCode: response.status, requestId: crypto.randomUUID(), timestamp: new Date().toISOString(), }), }) } return response}Best practices:
- Use middleware only when the deployment has request-time rendering.
- Keep secrets in the server runtime, never in client islands.
- Await
next()before sending telemetry so the final status code is available.
Verification:
curl -A "GPTBot" https://your-domain.com/- Confirm middleware runs in production.
- Refresh AI Analytics status.
Troubleshooting:
- If no crawler events arrive, verify the route is not fully prerendered static output.
- For static builds, move the same REST call to the hosting edge layer.
- If status codes are missing, ensure the middleware awaits
next().