SvelteKit installation

This page has two tiers: the install line that measures browser pageviews, then the app-hook forwarder that observes requests that never run JavaScript.

What you get

Every page load and client-side navigation that changes the pathname is measured as a pageview. The line cannot block rendering, interfere with your router, or set cookies. The forwarder observes requests without blocking, redirecting, or changing responses.

Prerequisites

You need Where it comes from
Your site key (pk_…) Onboarding
Managed-proxy hostname (<label>.<your-domain>) and its CNAME target We name the record at onboarding, then confirm when the hostname is active
Ingest URL and forwarder secret (sk_…) Onboarding
Node.js runtime with writable persistent storage Required for the forwarder’s durable queue

The managed proxy is decided and in build; onboarding is concierge today. For a strict same-origin Content Security Policy, use the rewrite alternative in step 3.

Install steps

1. Add the CNAME record, then wait for confirmation

Create the single CNAME record we name for <label>.<your-domain>. Wait until we tell you the hostname is active before adding the line.

2. Add the install line to src/app.html

src/app.html is the shell SvelteKit renders every page into, so one edit there covers your whole app. Add the line inside <head>, above the %sveltekit.head% placeholder:

<head>
  <meta charset="utf-8" />
  <script defer src="https://<label>.<your-domain>/px/t.js" data-site="pk_your_site_key"></script>
  %sveltekit.head%
</head>

Replace pk_your_site_key with your own site key. Anywhere in the document works — <head> is simply the one place guaranteed to be in every page of the app. What matters is the meaning, not the exact bytes: a deferred script sourcing /px/t.js and carrying your data-site.

Strict Content Security Policy alternative. Keep the same install-line semantics but use this same-origin form and configure the platform or CDN in front of your site to rewrite /px/* to the managed proxy:

<script defer src="/px/t.js" data-site="pk_your_site_key"></script>

This is a permanent alternative, not an old form. If you must run the relay yourself, use the Node proxy reference.

This line is frozen. It is stable for the life of your installation; no future version will ask you to edit it.

3. Add the forwarder

npm install @aurascope-analytics/forwarder-app-hook

In src/hooks.server.ts, guard the build and observe every runtime request:

import { building } from "$app/environment";
import type { Handle } from "@sveltejs/kit";
import { createAuraScopeMiddleware } from "@aurascope-analytics/forwarder-app-hook";

const auraScope = createAuraScopeMiddleware();

export const handle: Handle = async ({ event, resolve }) => {
  if (!building) auraScope(event.request);
  return resolve(event);
};

This tier requires a Node runtime and writable filesystem; it does not run on Edge adapters. Match every path, or accept that unmatched paths are unobserved. status ships as null in this version because observation happens before the response.

New crawlers can appear in your history retroactively as registries update. Crawler fetches are requests, not pageviews; AI-referral counts are always at least the displayed number. Unusual setup?

4. Rebuild and deploy

vite build, then deploy through whichever adapter you already use. The line is plain markup in the shell, so every adapter — static, Node, or a platform adapter — serves it identically.

Verify your install

  1. The line survived the build. From your build output, or against the deployed site:

    curl -sS https://www.your-site.example/ | grep '/px/t.js'

    Expect the line itself back.

  2. The script is served.

    curl -sS -D - -o /dev/null https://<label>.<your-domain>/px/t.js

    Expect 200 and content-type: text/javascript; charset=utf-8.

  3. Navigation is measured. Open your app with DevTools → Network. You should see t.js return 200, then one /px/e request return 204 on the first load. Then:

    What you do What you should see
    Click a link to another route One more e, 204
    Click a link that only changes the query string (a filter, a sort) Nothing — this is deliberate
    Press the browser back button onto a different path One more e, 204
    Reload One more e, 204
    Click an in-page anchor (#section) † Nothing — this is deliberate

    A pageview is a pathname change. Query-string and hash changes are not pageviews, so a faceted list page does not inflate your counts as a visitor refines it.

    † Every row above except the last was driven in the end-to-end run this guide is written from. The in-page-anchor row is the one behaviour we assert from the snippet’s rules and its own test suite rather than from that browser run — it holds for the same reason the query-string row does, but say so if you see otherwise.

  4. Confirm the forwarder. Log auraScope.forwarder?.metrics() after traffic: observed climbs, accepted catches up, and dropped stays 0.

  5. End-to-end confirmation. There is no self-serve install checker yet, so the last step — your test pageviews visible as durably stored rows — comes from us during onboarding. Ask once steps 1–3 pass. This is the only step that catches a misconfigured relay behind your /px/* routes: your browser still sees 204 either way, because the measurement path is deliberately fail-open.

Failure modes — and what they do to your site

Nothing on your site waits for AuraScope. The install line is deferred, and the forwarder copies request metadata then queues and ships it off the response path. If the service is unreachable it pauses, retains, and resumes; only capacity exhaustion drops oldest events, and that loss is counted.

Troubleshooting

/px/t.js returns 404

The line is installed but the managed-proxy hostname is not active, or the same-origin rewrite is absent. Check the onboarding confirmation and the platform/CDN rewrite.

The first load is measured, later navigations are not

The script did not load, or loaded from a page that then hard-navigated away. Confirm t.js returned 200 on the first load, and that your links are ordinary SvelteKit navigations rather than full document loads (a full load is still measured — it will just look like a first load every time).

A filter or sort click sends nothing

That is correct behaviour, not a fault. Only pathname changes count.

Two e requests fire on one navigation

The line is in the document twice — commonly once in src/app.html and once added to a layout component as well. Keep exactly one.

Uninstall

Remove the line from src/app.html, rebuild, and deploy. That is everything: nothing was installed on your server, and nothing was stored in your visitors’ browsers.

nic@aurascope.co