Web analytics (Beta)
One script tag gives any website pageviews, custom events and funnels. It is separate from the iOS SDK and from attribution links: this is product analytics for the web, scoped to a web app and its own tracking ID.
1. Create a web app
In the dashboard, find the Web analytics section and create a web app. It has its own tracking ID, shaped like naut_xxxxxxxx — a mobile app’s ID will not work here, and vice versa.
2. Add the snippet
Paste this into your <head>, on every page you want counted:
<script src="https://www.astronaut.sh/w.js?tracking_id=naut_xxxxxxxx"></script>
A data- attribute works too, if a tag manager or CSP makes the query string awkward:
<script src="https://www.astronaut.sh/w.js" data-tracking-id="naut_xxxxxxxx"></script>
The tracking ID is public by design — it only says which app an event belongs to, exactly like a mobile tracking ID. There is nothing to keep secret and no key to rotate.
3. What you get for free
On load the snippet records a pageview, and records another on every SPA route change — it wraps history.pushState and replaceState and listens for popstate, so client-side routers are handled without any wiring. Repeated navigations to the same path are ignored.
Each event also carries, automatically:
- a
visitor_idthat persists across visits, and asession_idthat lasts the tab session — both random, first-party, and kept inlocalStorage/sessionStorage. No cookies, so no cookie banner is needed for them. - the path, the referrer, and any
utm_*parameters on the URL. Traffic source is taken fromutm_sourcewhen present, and inferred from the referrer otherwise. - country/region/city resolved from the request, and a hashed IP. The raw IP is never stored.
4. Custom events
The snippet defines a global naut(). Call it with an event name and, optionally, a flat object of properties:
naut("signup_completed", { plan: "pro" });Name events in lowercase snake_case. Names are normalised by stripping every character outside a-z 0-9 _ - and truncating to 64 characters — and stripping means removed, not replaced. So "Signup Completed" arrives as signupcompleted, and "checkout.step1" as checkoutstep1. Names that differ only in punctuation will silently merge into one event.
5. Choosing names over properties
Properties are stored with the event and shown on the per-visitor activity feed, but the dashboard aggregates on the event name alone — nothing groups by a property. An event you want to compare or funnel on should carry that distinction in its name:
// Reads as one undifferentiated row in the dashboard:
naut("section_view", { section: "pricing" });
// One row per section, chartable and selectable in the funnel builder:
naut("section_view_pricing");Keep properties for detail you want when inspecting a single visitor — a plan name, a team size, a variant — and put the dimension you intend to measure in the name.
6. Purchases
An event named exactly purchase is special: its revenue and currency properties are lifted into dedicated columns and roll up into revenue reporting. Every other event leaves them empty.
naut("purchase", { revenue: 9.99, currency: "USD" });7. Calling naut() before the script loads
If your app may fire an event before the snippet has finished loading, define a queue stub first. The snippet flushes anything queued on naut.q when it initialises, so nothing is dropped:
window.naut = window.naut || function () {
(window.naut.q = window.naut.q || []).push(arguments);
};8. Next.js and other frameworks
The snippet finds its own tag even when a loader injects it asynchronously, so next/script works as-is:
import Script from "next/script"; <Script src="https://www.astronaut.sh/w.js?tracking_id=naut_xxxxxxxx" strategy="afterInteractive" />
Gate it on your production environment if you would rather not count local development traffic — the snippet itself does not know which environment it is running in.
Limits and behaviour
- Properties are capped at roughly 8 KB once serialised. A larger object is dropped rather than truncated, and the event is still recorded without it.
- Events are sent with
navigator.sendBeacon, falling back tofetchwithkeepalive, so they survive the page being closed mid-navigation. - A missing or unknown tracking ID means the snippet does nothing at all — it fails silently rather than logging to your users’ console.
Web analytics is in beta and enabled per account. If the Web analytics section is not on your dashboard yet, it has not been turned on for you. See The dashboard for reading the reports, and the privacy policy for what is stored.