# SvelteKit Installation Guide

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

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

This is the canonical Virent installation guide for SvelteKit applications.

### Install with your coding agent

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

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

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

Install two separate things:
1. Human Analytics (browser): Install the browser script in SvelteKit's app.html template.
2. AI Crawler Analytics (server): Add crawler tracking to the SvelteKit server handle hook.

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 script to src/app.html so it is present for every rendered document.
- Keep the tag in the head near %sveltekit.head% and keep the publishable key unchanged.
- Use the npm SDK from client modules for custom events only.

AI Crawler Analytics best practices:
- Use src/hooks.server.ts and the handle hook to observe every SvelteKit request.
- Resolve the response before sending crawler telemetry so the final status code can be included.
- Keep the server secret in private environment variables.

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:
- Start or preview the SvelteKit app.
- Inspect the rendered head and Network requests.
- Refresh Human Analytics status.
- Run the SvelteKit app with a server adapter.
- Request a route with curl -A "GPTBot".
- Refresh AI Analytics status.
```

## Research Notes

- `src/app.html` is the global HTML template for SvelteKit.
- `src/hooks.server.ts` can define a `handle` hook that runs for server requests.
- Server hooks do not run for prerendered pages or static assets, even on server deployments.

Primary references:

- https://svelte.dev/docs/kit/hooks

## Human Analytics

Add the script to `src/app.html`.

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <script
      defer
      data-write-key="vha_pk_..."
      src="https://app.virent.app/js/script.js"
    ></script>
    %sveltekit.head%
  </head>
  <body data-sveltekit-preload-data="hover">
    <div style="display: contents">%sveltekit.body%</div>
  </body>
</html>
```

Best practices:

- Keep the script near `%sveltekit.head%`.
- Use public env values only for publishable browser keys.
- Use the npm SDK from client modules only when custom events are needed.

Verification:

- Start or preview the app.
- Inspect the rendered head and Network requests.
- Refresh Human Analytics status.

Troubleshooting:

- If the script is absent, confirm `src/app.html` is the active template.
- If CSP blocks Virent, update deployed CSP headers.
- If localhost testing 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

Set private environment values:

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

Add or compose a server hook:

```ts
// src/hooks.server.ts
import { env } from "$env/dynamic/private"
import type { Handle } from "@sveltejs/kit"

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 const handle: Handle = async ({ event, resolve }) => {
  const response = await resolve(event)
  const userAgent = event.request.headers.get("user-agent") ?? ""

  if (!staticAssetPattern.test(event.url.pathname) && aiCrawlerPattern.test(userAgent)) {
    await fetch(env.VIRENT_INGEST_ENDPOINT, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-api-key": env.VIRENT_INGEST_SECRET,
      },
      body: JSON.stringify({
        siteId: env.VIRENT_SITE_ID,
        url: event.url.toString(),
        host: event.url.host,
        path: event.url.pathname,
        method: event.request.method,
        referer: event.request.headers.get("referer"),
        userAgent,
        statusCode: response.status,
        requestId: crypto.randomUUID(),
        timestamp: new Date().toISOString(),
      }),
    })
  }

  return response
}
```

Best practices:

- Compose with existing auth, i18n, or observability hooks instead of replacing them.
- Resolve the response first so the final status code is available.
- Use the hosting edge or reverse proxy for prerendered pages, static assets, and static-adapter deployments.

Verification:

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

- Confirm the hook runs.
- Refresh AI Analytics status.

Troubleshooting:

- If hooks do not run, confirm the deployed adapter supports server-side requests.
- If existing hooks break, compose handles in sequence.
- If secrets are undefined, verify private env loading in the platform.
