Add analytics to SvelteKit
A cookieless tracker for SvelteKit, installed once in the app template and left alone.
The install
SvelteKit has one HTML file that wraps every route. Put the snippet in the
<head> of src/app.html:
<!-- src/app.html -->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
%sveltekit.head%
<script defer data-site="YOUR_SITE_CODE" src="https://www.staats.ai/s.js"></script>
</head>
<body data-sveltekit-preload-data="hover">
<div>%sveltekit.body%</div>
</body>
</html>
Put it in app.html rather than a +layout.svelte. The app template is
rendered once per document, so the tag is emitted as plain HTML and never re-evaluated. A tag placed
in a root layout can re-run on client navigation, which is where duplicate pageviews come from.
Client-side navigation is already covered
SvelteKit intercepts link clicks and navigates on the client, so most page changes never trigger
a document load. You do not need to subscribe to afterNavigate or watch
$page.url to record them. The tracker hooks pushState,
replaceState and popstate, which is what the SvelteKit router uses, so
soft navigations are counted the same as hard ones.
Adding an afterNavigate hook on top will double-count every in-app navigation.
Preloading does not inflate your numbers
The default data-sveltekit-preload-data="hover" fetches a route's data when a visitor
hovers a link, before any click. That is a data load, not a navigation: no history entry is pushed
and no pageview is recorded. Hovering every link in your nav will not show up as traffic.
Tracking a click
Add a data-track attribute to any clickable element in a .svelte file.
The tracker uses one delegated listener, so this works on elements rendered conditionally or inside
an {#each} block:
<button data-track="signup_click">Start free</button>
For form submissions, or anything that is not a click, call window.track from your
handler:
<script>
function onSubmit() {
window.track('contact_submitted')
}
</script>
No cookie banner
The script is around 1.5KB, sets no cookies, and stores nothing on the device. Visitors are counted with a pseudonymous hash that rotates daily, and country comes from the browser timezone instead of an IP lookup. There is nothing to consent to, so no banner.
Local development
Leave the snippet in app.html while you work. localhost is never
recorded, so vite dev stays out of your production numbers without an environment
check.
Worth knowing: src/routes/+error.svelte renders inside the same
app.html, so 404s are recorded as pageviews for paths that do not exist. If mistyped
URLs and crawler probes cluttering your top pages bothers you, filter those paths when you ask, or
serve your error page from a template without the snippet.
Reading the data
Staats has no dashboard. You connect it to your coding agent once, and then you ask. Because the connection is a Model Context Protocol server, the agent can pull traffic, referrers, and funnels into the conversation you are already having about the code.
> did the new pricing page help?
The agent also stores what the site is for and what each event means, so a question three weeks later does not start from nothing. See MCP Server and Agent Workflows.
Get a site code
Create a free account, add your site, and paste the snippet above. The free plan covers side projects, and there is no card required.