# Pageviews and single-page apps

When ProductLense records a pageview, how client-side route changes are tracked, and how to send one by hand.

ProductLense records a pageview when the script loads and every time the URL changes inside a single-page app. You do not need router hooks for Next.js, React Router, SvelteKit, Nuxt, Astro's client router or Framer page transitions.

## What counts as a pageview

- One pageview when the page loads.
- One pageview after `history.pushState`, `history.replaceState` and on `popstate`, but only when the cleaned URL differs from the last one recorded.

The cleaned URL drops the `#hash` and every query parameter except `utm_*`, `ref`, `gclid`, `fbclid` and `msclkid`. So changing only the hash, or adding a search parameter such as `?page=2`, does not record another pageview.

## Turn route tracking off

Set `data-spa="false"` to record only full page loads:

```html
<script defer data-site="SITE_ID" data-spa="false" src="https://api.productlense.app/a.js"></script>
```

## Send a pageview by hand

With route tracking off, or for a view that does not change the URL (a modal that acts as a page), call:

```js
window.productlense('pageview');
```

A manual pageview always records, even when the URL has not changed.

## What a pageview carries

| Field | Value |
|---|---|
| `u` | The cleaned page URL |
| `r` | `document.referrer` |
| `t` | `document.title` (stored up to 512 bytes) |
| `w` | Viewport width in pixels |
| `l` | Browser language |
| `uid` | Your user id, after `identify` in this tab |
| `ts` | Time in milliseconds |

Events are queued and sent in batches of up to 10, or after one second, whichever comes first. The queue is flushed when the page is hidden or closed, using `navigator.sendBeacon` with a `fetch` `keepalive` fallback.

## Timestamps

The server uses the browser's timestamp when it is no more than one hour in the past and one minute in the future; otherwise it uses the time the request arrived.

## Related

- [Custom events](/docs/track/custom-events)
- [Exclude pages](/docs/track/exclude-pages)
- [How ProductLense counts](/docs/get-started/how-productlense-counts)
