Most sites need no extra code. Pageviews feed Pages, Sources & channels and related reports; they are called views in web analytics and appear as $pageview in behavior analytics.
Automatic rules
After the script loads, a pageview is recorded at these moments:
| Moment | What happens |
|---|---|
| First load | Recorded after DOMContentLoaded. If the document has already finished parsing when the script loads, it is recorded immediately |
history.pushState / history.replaceState | The script wraps both methods and records on the next turn of the event loop |
popstate | Recorded on browser back and forward |
hashchange | Listened to only when data-hash="true" |
The same URL is not recorded twice in a row. If the new URL equals the last recorded URL, the call is ignored. A replaceState that changes state but not the URL does not add a pageview.
By default the # and everything after it are stripped, so /docs#intro and /docs#faq are the same page. If the site uses hash routing (for example /#/pricing), set data-hash="true" on the script. The hash is kept and hashchange is listened to.
Mainstream frameworks need no extra code
History mode in Vue Router, React Router, Next.js, and Nuxt all change routes through pushState / replaceState. Installing the script is enough. Framework notes are in Vue guide, React guide, Next.js guide, and Nuxt guide.
Parameters & methods
Script attributes
data-sitestringrequireddata-hashstringDefault: hash is droppeddata-excludestringDefault: emptydata-auto-trackstringDefault: onJavaScript method
window.webcount.pageview() records one pageview from the current location. It takes no arguments; URL and title are read from the current page.
pageview() uses the same rules as automatic recording: a URL equal to the last one is ignored, a path matching data-exclude is ignored, and time on the previous page is sent before the switch.
Defaults & limits
- Excluded paths match the path only.
data-excludeuseslocation.pathname, without the query or hash. Exclusion applies to pageviews only.track()on the same page still sends. - Title. Taken from
document.title, at most 200 characters. After a route change the title is read on the next turn of the event loop. If the framework updates the title later, the recorded title may still be the previous page. The URL is not affected. - URL. Only
http:andhttps:pages are accepted, at most 4000 characters. The collector drops the query string and a trailing/when it stats a page, and the path is at most 512 characters. A URL that contains#/folds the hash into the path. - Referrer. The first load uses
document.referrer. After an in-app route change, the referrer is the previous in-site page. - Time on page. On a route change, when the tab hides (
visibilitychange), or when the page closes (pagehide), if the current page was open for more than 1 second the script sends a__leaveevent with the duration in milliseconds. - Deduping. The collector keeps one pageview when the same visitor repeats one within 2 seconds.
- No cookie. w.js itself writes neither a cookie nor local storage. How visitors are deduped is in Visitors, users & identity.
Examples
A hash-routed single-page app:
<script async src="https://app.tapcub.com/w.js" data-site="{{SITE_KEY}}" data-hash="true"></script>Skip the admin and preview paths:
<script async src="https://app.tapcub.com/w.js" data-site="{{SITE_KEY}}" data-exclude="/admin/*,/preview"></script>Fully manual pageviews (the router does not use the History API, or you want to record only after data has loaded):
<script async src="https://app.tapcub.com/w.js" data-site="{{SITE_KEY}}" data-auto-track="false"></script>
<script>
// Call this from your router's done callback
function onRouteReady() {
if (window.webcount) window.webcount.pageview();
}
</script>The cost of turning automatic collection off
data-auto-track="false" also turns off time on page, outbound and download clicks, Web Vitals, and 404 events. There is no separate manual method to put those back. To skip only some pages, use data-exclude.
Responses & side effects
- Each pageview sends one request to the collect endpoint (the script's origin plus
/api/v1/pulse). It prefersnavigator.sendBeaconand falls back tofetch(keepalive, no credentials). - The first pageview of a page load uses a
fetchthat can read the response. The response carries site config, which decides whether to load the chat widget or the identity script (w-id.js). - The collector always returns
202, including for dropped requests, so the status code does not tell you whether the hit was stored. How to verify is in Verify your installation.
Errors & compatibility
- Duplicate install. If the page includes two copies, only the first runs. The later one sees that
window.webcountis ready and exits, so it does not double-count. - Local development. On
localhost,127.*,0.0.0.0, and[::1]the script does not send by default. For local debugging adddata-allow-local="true", or setdata-apiexplicitly. Even then, a hostname that is not a registered website is dropped by the collector. - DNT / GPC. If the browser has Do Not Track or Global Privacy Control on, the script does not run by default. See CSP, blockers & networks.
- Silent failure. Errors inside the script are swallowed. They do not affect the page and they do not print to the console.
- Crawlers. A browser with automation signals (such as
navigator.webdriveror a headless user agent) attaches a signal on the request. The collector classifies it as a bot and counts it separately. See Bots & AI crawlers.