Skip to content

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.

npm install statable-analytics
// 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:

OptionTypeDefaultWhat it does
siteIdstring or numberrequiredNumeric Site ID
hoststringhttps://statable.comOrigin the tracker is served from, for proxying through your own domain
trackingApistring<host>/api/eventOverride the ingest endpoint
beforeSendstringnoneName of a global function that may adjust custom props before an event is sent
propsobjectnoneSticky custom properties added to every event from a page load
devbooleanfalseAlso load the tracker in astro dev
disabledbooleanfalseSkip 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>
# .env
PUBLIC_STATABLE_ID=3270462

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:

localStorage.setItem('analytics_ignore', 'true')

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 dev shows nothing unless you passed dev: true. That is expected.
  • The full checklist is in Verify installation.

Common pitfalls

  • A <script> tag without is: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. Pass dev: true to change that.
  • A Site ID without the PUBLIC_ prefix. Astro keeps other environment variables on the server, so the value arrives as undefined and 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.