Astro is the easiest of these to install on and has one detail that is counter-intuitive: the tag goes in the <head>, not before the closing </body> tag the way the general instructions say. That is not a preference. It is what makes the tag survive a client-side navigation, and the reason is at the bottom of this page.
Get the snippet from your dashboard first. It looks like this, with your own site ID in the path:
<script src="https://t.skomi.com/s/your-site-id.js" defer></script>
Put it in the shared layout
In whichever layout every page uses, usually src/layouts/Layout.astro, inside the <head>:
---
// src/layouts/Layout.astro
---
<html lang="en">
<head>
<meta charset="utf-8" />
<slot name="head" />
<script is:inline defer src="https://t.skomi.com/s/your-site-id.js"></script>
</head>
<body>
<slot />
</body>
</html>
is:inline matters. Astro processes <script> tags, bundling them, converting them to modules and compiling TypeScript, for any tag whose only attribute is src. is:inline tells it to render the tag into the HTML exactly as written, which is what you want for a script served from somebody else's domain. (Any other attribute implicitly adds is:inline too, so the defer above would do it on its own; writing it out says what you meant.)
If you use <ClientRouter />
Astro is a multi-page app by default: every navigation is a real page load, and there is nothing else to think about.
Add <ClientRouter />, which is view transitions, and it becomes a client-side router. Skomi handles that without any help: the router navigates with history.pushState (and replaceState, where a link asks it to), and Skomi's script wraps both and listens for popstate, so each navigation is a page view. The page being left is finished first, so its time on page belongs to it rather than to the next one.
⚠ Do not add data-astro-rerun to this tag. That attribute exists to force an inline script to run again after every transition, which here means booting the tracker a second, third and fourth time, and counting every visit that many times over. Leave it off.
Why the head, and not before </body>
Astro's documentation is precise about what a view transition does to the document, and the two halves are treated oppositely:
- The
<body>is completely replaced with the new page's body. - In the
<head>, "scripts are left in if they exist on the new page".
So a tag at the end of the body is removed and re-inserted on every single navigation, which re-runs it, while the same tag in the head of a layout every page shares is recognised as already present and left alone. It runs once, on the first load, and keeps working from there.
On a site with no <ClientRouter /> both positions behave identically, so the head is the right answer either way and there is nothing to change if you add view transitions later.
Check it is working
Load a page on the real domain, navigate to a second page, then back to the first. You should see three page views, one per navigation, not six. Six means the tag is being re-executed, which means it is in the body or carries data-astro-rerun.
If nothing arrives at all, see what to check when the snippet is not working.
Two things that will look broken and are not
Nothing is collected from localhost. Your development address is not the domain you registered, so hits from it are refused by the origin check. Deploy to the real domain, or a subdomain of it, and look there.
A Content Security Policy will block it until told not to. The script and the data it sends use one host:
script-src https://t.skomi.com
connect-src https://t.skomi.com