Astro
Astro ships zero JavaScript by default and renders pages to static HTML at build time. Statable is a client-only script, so it goes in <head> and runs once the page is parsed. Astro's view transitions use the History API, which Statable follows on its own.
This guide covers Astro 4 and 5.
Install
Approach 1: the statable-analytics integration
One line in the config, and every page Astro builds carries the tracker.
// astro.config.mjs
import { defineConfig } from 'astro/config'
import statable from 'statable-analytics/astro'
export default defineConfig({
integrations: [statable({ siteId: '3270462' })],
})
Replace 3270462 with the numeric Site ID from Site settings → Tracking Code.
astro dev does not load the tracker, so local browsing stays out of your numbers. Pass dev: true when you want it there too.
Options, each mapping to an attribute in the tracking script reference:
| Option | Type | Default | What it does |
|---|---|---|---|
siteId | string or number | required | Numeric Site ID |
host | string | https://statable.com | Origin the tracker is served from, for proxying through your own domain |
trackingApi | string | <host>/api/event | Override the ingest endpoint |
beforeSend | string | none | Name of a global function that may adjust custom props before an event is sent |
props | object | none | Sticky custom properties added to every event from a page load |
dev | boolean | false | Also load the tracker in astro dev |
disabled | boolean | false | Skip injection entirely |
Approach 2: a script tag in your layout
No dependency. Put the tag in the <head> of the layout every page uses.
---
// src/layouts/Layout.astro
const siteId = import.meta.env.PUBLIC_STATABLE_ID
---
<html lang="en">
<head>
<script is:inline defer src={`https://statable.com/js/${siteId}/s.js`}></script>
</head>
<body>
<slot />
</body>
</html>
is:inline is the part people miss. Without it Astro treats the tag as application code, bundles it and rewrites the src, and the tracker never loads from our domain. See Astro's script reference for what the directive does.
Only variables prefixed with PUBLIC_ reach the browser in Astro.
Tracking custom events
The tracker exposes window.statable once it has loaded. The script is deferred, so guard the call:
<button id="signup">Sign up</button>
<script>
document.getElementById('signup')?.addEventListener('click', () => {
window.statable?.t?.('Signup', { plan: 'pro' })
})
</script>
For TypeScript, declare the global once in src/env.d.ts:
declare global {
interface Window {
statable?: {
t: (event: string, props?: Record<string, unknown>) => void
}
}
}
export {}
Event naming is covered in Custom events.
Tracking page views
A static Astro site does a full page load per navigation, so each one is an ordinary pageview.
With <ClientRouter /> (view transitions) the document is swapped without a reload. Astro uses the History API for that, and Statable counts the pageview without extra configuration.
Excluding internal traffic
Statable respects a per-browser opt-out flag in localStorage. To keep your own visits out, run this once in DevTools on the live site:
Remove it with localStorage.removeItem('analytics_ignore'). Full procedure in Verify installation.
Verify it's working
- Open Statable Realtime in your dashboard.
- Build and preview the site, then move between pages. Each view should appear within seconds.
npm run devshows nothing unless you passeddev: true. That is expected.- The full checklist is in Verify installation.
Common pitfalls
- A
<script>tag withoutis:inline. Astro bundles and rewrites processed scripts, so the tracker silently never loads. This is the single most common mistake with Approach 2. - Mixing both approaches. The tracker then loads twice. The second copy exits at once, so nothing is counted twice, but it is a wasted download. Pick one.
- Expecting numbers from
astro dev. The integration skips the dev server on purpose. Passdev: trueto change that. - A Site ID without the
PUBLIC_prefix. Astro keeps other environment variables on the server, so the value arrives asundefinedand the URL points nowhere. - Putting the tag in
<body>. The tracker registers early to capture engagement. Keep it in<head>.
See also: Vite, Install the tracking script, Custom events, JavaScript API.
Ready to take control of your web analytics? Try Statable free for 30 days. No credit card required, full feature access, built for GDPR. Start your free trial or view a live demo.