Frameworks · 2 min setup
How to add analytics to an Astro site
is:inline in the <head> of the layout every page uses (usually src/layouts/Layout.astro). Static pages are counted on load, and with <ClientRouter /> view transitions each soft navigation is counted too.Updated
Astro processes and bundles <script> tags by default. An analytics tag must reach the browser exactly as written — external src, defer, and the data-site attribute the tracker reads — so mark it is:inline. (Astro already treats any script with attributes besides src as inline; writing it explicitly just makes the intent obvious to the next person.)
Astro at a glance
- Where the tag goes
<head>of your base layout, e.g.src/layouts/Layout.astro- Directive
is:inlineso Astro leaves the tag untouched- View transitions
<ClientRouter />navigations tracked (History API)- Output modes
- Static, server and hybrid — and any adapter
- Content collections / MDX
- No changes needed; they render inside the layout
How to install VisitTrack on Astro
Step 1: Copy your site id
Add the site in VisitTrack and copy the snippet from the install step (or Settings → General). Replace
SITE_IDbelow.Step 2: Add the tag to your base layout
Open the layout that renders
<html>and<head>for every page. If you have several layouts, put it in the one they all extend — or in a sharedBaseHead.astrocomponent used by each.src/layouts/Layout.astro--- const { title } = Astro.props; --- <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width" /> <title>{title}</title> <script is:inline defer data-site="SITE_ID" src="https://visitrack.app/tracker.js"></script> </head> <body> <slot /> </body> </html>Step 3: Using view transitions? Keep the tag in the head only
With
<ClientRouter />, Astro swaps the page in the browser and compares the old and new<head>. Because every page renders the identical tag from the same layout, it's kept as it is rather than run again. Don't adddata-astro-rerunto it — that forces a re-run on every navigation and would double-count.Step 4: Build, deploy and open the site
astro devserves on localhost, which the tracker ignores. Deploy (or runastro previewwithdata-allow-localtemporarily) and browse a few pages.
How to check VisitTrack is working on Astro
- View source on a deployed page: the tag appears in
<head>exactly as written, not rewritten into a bundled module. - Open two or three pages; the VisitTrack live view shows you, and the Pages tab lists each path.
- With
<ClientRouter />, DevTools → Network should show exactly onePOSTtovisitrack.app/api/collectper navigation, not two.
Does VisitTrack track Astro page navigation?
Without view transitions, each page is a full page load and gets its pageview on load. With <ClientRouter />, Astro's router updates the URL with history.pushState (or replaceState for data-astro-history="replace" links), which the tracker wraps — so soft navigations are pageviews too. You don't need an astro:page-load listener for VisitTrack.
Pageviews are keyed on the pathname, so a link that only changes the #hash or the query string isn't counted again.
How to track custom events and goals in Astro
On a mostly static site, the attribute-based events do most of the work: data-vt-goal="newsletter_cta" records a click on any element, and data-vt-scroll="pricing_viewed" fires once when that element is at least half visible. Both are re-scanned after client-side navigations.
For logic, call window.visitrack() from a client script. In a regular (bundled) Astro <script>, it runs once per page load; with view transitions, put per-page code in an astro:page-load listener.
<a href="/pricing" data-vt-goal="pricing_cta">See pricing</a>
<form id="newsletter">…</form>
<script>
document.addEventListener("astro:page-load", () => {
const form = document.getElementById("newsletter");
form?.addEventListener("submit", () => window.visitrack?.("newsletter_signup"));
});
</script>Automatic events are on by default too: file downloads, mailto:/tel: clicks, form submits, rage clicks, JavaScript errors and 404 pages (Astro's 404.astro title usually contains "404" or "not found", which is what the tracker checks).
Script options you might need on Astro
All optional — add them to the same tag. Full reference: script configuration.
| Attribute | What it does |
|---|---|
data-cookieless | Store nothing in the browser; the server derives a daily-rotating id. See cookieless mode. |
data-exclude="/app/*" | Never track these paths (comma-separated, * wildcard). Checked on every navigation. |
data-allow-local | Track on localhost and *.local — for testing an install only. |
data-debug | Log every event the script sends, and why it skipped one, to the console. |
data-auto="false" | Turn off automatic events (downloads, contact clicks, form submits, rage clicks, JS errors, 404s). |
Astro troubleshooting
The tag shows up as a bundled type="module" script
Astro processed it. Add is:inline and make sure the tag has its data-site attribute.
Double pageviews with view transitions
Look for a second copy of the tag in a page or component body, or a data-astro-rerun attribute. Keep exactly one tag, in the layout's <head>.
Some pages aren't tracked, or counts jump after visiting them
Those pages use a different layout without the tag. With view transitions, navigating from such a page back to one with the tag runs the script a second time. Move the tag into a shared head component that every layout includes.
CSP blocks the script
Add https://visitrack.app to script-src and connect-src wherever your policy is set (host headers, middleware or a meta tag).
Astro analytics FAQ
Why does the VisitTrack script need is:inline in Astro?
Without it, Astro may treat the script as one to process and bundle. The tracker reads its configuration (data-site and options) from its own script tag and must load from visitrack.app, so it has to reach the browser unchanged.
Does VisitTrack work with Astro view transitions (ClientRouter)?
Yes. The router uses the History API, which the tracker listens to, so each soft navigation is recorded as a pageview with no extra code. Keep the tag in the shared head and don't mark it data-astro-rerun.
Is there an Astro integration package for VisitTrack?
No; a single tag in the layout is the whole install. That keeps it working across Astro upgrades and with any adapter.
Does VisitTrack work with Astro's static output on GitHub Pages or Netlify?
Yes. The tag is plain HTML in every generated page, so it works on any static host.
Will it affect my Astro site's Lighthouse score?
The script is about 5 KB gzipped and loads with defer, so its impact should be minimal. VisitTrack also collects real-user Web Vitals, so you can see field data rather than lab scores.
Keep going
- SvelteKit analyticsAdd VisitTrack to SvelteKit by adding one tag to src/app.html — client-side navigations are tracked, and kit.csp gets two extra sources.
- Hugo analyticsAdd VisitTrack to a Hugo site through your theme's head partial, loaded only in production with hugo.IsProduction, plus a shortcode for goals.
- Gatsby analyticsAdd VisitTrack to Gatsby with setHeadComponents in gatsby-ssr — rendered into every page's head, with Gatsby Link navigations tracked automatically.
- Netlify analyticsAdd VisitTrack on Netlify with Snippet injection (no code) or in your framework, keep deploy previews out, and set CSP in _headers.
- Install the tracking scriptThe reference tag, CSP notes and how to check it's working.
- Custom events and signupsvisitrack(), data-vt-goal, scroll events and server-side signups.
- Use casesHow SaaS teams, indie hackers, stores and agencies use VisitTrack.
- VisitTrack vs Google AnalyticsCookie-free analytics with revenue attribution, compared honestly.
Add VisitTrack to your Astro site
Cookie-free analytics with revenue attribution, live visitors, funnels and session replays. One script tag, every feature on every plan. 14 days free, no card required.
Not on Astro? See all 29 integration guides.