Pageviews & SPAs
The first pageview
Section titled “The first pageview”Unless data-auto="false" is set, the tracker sends one pageview as soon as it
can: immediately if the document is already parsed, otherwise on
DOMContentLoaded.
It is held — along with anything you queued — until the identity probe answers or 1500 ms elapse, whichever is first. That wait is what keeps the landing hit, and therefore the campaign that produced the visit, filed under the same visitor as everything after it.
Single-page apps
Section titled “Single-page apps”The tracker patches history.pushState and listens for popstate, so any
router that navigates through the History API is tracked with no extra code.
That covers:
- Next.js (App Router and Pages Router)
- React Router, TanStack Router, Wouter
- Vue Router, Nuxt
- SvelteKit
- Astro View Transitions
- Angular Router
Hash routers
Section titled “Hash routers”Hash-only navigation does not touch the History API. Opt in:
<script defer data-site="pk_live_xxxxxxxx" data-hash="true" src="https://ingest.tracing.tools/t.js"></script>With data-hash="true" the tracker also listens for hashchange and includes
the hash when deciding whether the path changed.
replaceState is not a pageview
Section titled “replaceState is not a pageview”Only pushState and popstate trigger one. Replacing the URL to keep a filter
or a scroll position in sync — which is what replaceState is for — correctly
does not count as a new page.
Deduplication
Section titled “Deduplication”A pageview is skipped when the computed path is identical to the last one sent:
location.pathname + location.search + (data-hash ? location.hash : "")So a router that fires twice for one navigation produces one pageview, and
pushState to the same URL produces none.
Note that the query string is part of that comparison, even though the
dashboard groups by path. /search?q=a then /search?q=b are two pageviews
that both appear under /search.
Manual pageviews
Section titled “Manual pageviews”tracing.pageview();Useful when you set data-auto="false" because you need to wait for something
— a consent decision, a locale redirect, an A/B assignment — before the first
hit. Deduplication still applies, so calling it twice for the same URL sends
one hit.
What is recorded
Section titled “What is recorded”| Stored as | From |
|---|---|
| Path | location.pathname, / if empty |
| Query | location.search with utm_*, ref, source, via, fbclid, gclid, gbraid, wbraid, msclkid and ttclid removed; null if nothing is left |
| Title | document.title |
| Hostname | the host of the URL |
| Referrer | document.referrer, plus a bare referrer domain with www. stripped, null when it is your own host |
| UTM | utm_source/medium/campaign/term/content, with ref, source or via as a fallback for utm_source |
| Language | navigator.language |
| Screen | window.innerWidth × innerHeight |
| Browser, version, OS, device | parsed from the user agent server-side |
| Country, region, city | resolved server-side — see Geo |
Splitting the query off the path is what keeps /pricing?utm_source=hn and
/pricing the same row on the dashboard, while /search?q=cookies keeps its
term for the Query dimension.
Time on page
Section titled “Time on page”Two mechanisms fill in how long a page was looked at.
On the next pageview. The tracker measures the gap since the last pageview
and sends it as d on the new hit. That figure lands on the session total.
On page hide. When visibilitychange fires with visibilityState === "hidden", the tracker sends a duration hit by sendBeacon with the elapsed
time. Coming back to the foreground resets the clock, so time spent in a
background tab is not counted, and an elapsed time under one second is not sent
at all.
A flushed duration is clamped to between 0 and 30 minutes, the same idle window that ends a visit.
What “bounce” means here
Section titled “What “bounce” means here”A visit is a bounce while it has one or fewer pageviews. It stops being one the moment a second pageview arrives. Custom events, outbound clicks and revenue events do not clear the bounce flag — which is deliberate: a single-page visit where somebody converted is still a single-page visit, and Sessions has a Converted chip for exactly that case.