Install

Add analytics and revenue attribution to SvelteKit

SvelteKit's shell is src/app.html, and that's the only file this install touches. Client-side navigation is handled by the snippet, so there's no afterNavigate hook to maintain.

Start a 14-day trialLast reviewed August 2026
  1. Add the snippet to app.html

    src/app.html wraps every route, server-rendered or client-navigated. Put the tag before the closing </body> alongside %sveltekit.body%.

    src/app.html
    <body data-sveltekit-preload-data="hover">
        <div style="display: contents">%sveltekit.body%</div>
        <script defer src="https://revtrail.pyln.dev/track.js" data-site="SITE_KEY"></script>
      </body>
  2. Skip the afterNavigate hook

    SvelteKit's client router uses the History API, which the script already watches. Adding an afterNavigate handler that fires a pageview manually is the most common way to end up with every navigation counted twice.

  3. Confirm it's receiving

    Open your site in a normal browser tab, then check Realtime in the Revtrail dashboard — your own visit should appear within a few seconds. If nothing arrives, the usual causes are a mistyped site key, an ad blocker on your own browser, or a Content-Security-Policy that blocks the script origin.

  4. Instrument the conversion that matters

    Pageviews tell you traffic; a goal tells you whether the traffic worked. Call revtrail() at the moment of signup, trial start, or booking, then register it as a goal in the dashboard. This is the one step people skip, and it's the step that makes every other number worth reading.

    anywhere in your client code
    revtrail('signup')
  5. Wire revenue in from Stripe

    Point a Stripe webhook at Revtrail for checkout.session.completed and invoice.payment_succeeded, save the signing secret in your site settings, and pass the visitor id as the Checkout session's client_reference_id. Payments then attach to the visitor's first-touch channel, including renewals months later. Revenue is never accepted from the browser, so this webhook is the only way money reaches your dashboard.

    when you create the Checkout session
    const visitorId = await revtrail.visitorIdAsync();
    // best-effort: omit when null, never block checkout on analytics
    stripe.checkout.sessions.create({
      client_reference_id: visitorId ?? undefined,
      // …line items, success_url, etc.
    });

Form actions and server-side conversions

If a signup completes through a form action, the conversion happens on the server and there's no client code left to call revtrail(). Two options: fire the event from the page after a successful enhance() callback, or send it server-side by POSTing to the ingest endpoint with the visitor id you carried through the form.

Adapters make no difference

Node, Vercel, Cloudflare, static — the adapter changes how your app is served, not how a browser script behaves. The one adapter-specific consideration is the first-party proxy: how you rewrite /rt/track.js depends on your host, not on SvelteKit.

Questions people actually ask

Where exactly does the snippet go in a SvelteKit project?
src/app.html, before the closing body tag. Not in a +layout.svelte — that would re-run on navigation and is unnecessary.
Do I need to track navigations manually?
No, and you shouldn't — manual afterNavigate tracking on top of the automatic History API tracking double-counts every navigation.
Do I need a cookie banner for this?
Not for Revtrail. The default identity is a daily rotating hash — no cookies, no persistent identifier, nothing stored on the device. If you run ad pixels or other analytics that do use cookies, their obligations are unchanged.
Will this slow my site down?
The script is deferred, so it never blocks rendering, and it's about 3KB over the wire gzipped. It fires one beacon per pageview.

Install guides

All install guides