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.
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>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.
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.
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 coderevtrail('signup')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 sessionconst 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