// context projectglobal

Pageviews & SPA Tracking

When data-analytics="pageviews" (or all) is set, the SDK fires a pageview event on every page load. For single-page apps, add spa to capture client-side route changes too.

Initial pageview

Fires once on every fresh page load, after DOMContentLoaded (so document.title is final). Payload:

{
  "event_type": "pageview",
  "url": "https://example.com/pricing",
  "path": "/pricing",
  "referrer": "https://news.ycombinator.com/",
  "visitor_id": "01HZ7XK4N8...",
  "session_id": "01HZ7XK4P2...",
  "timestamp": "2026-05-14T10:00:00.000Z",
  "custom_props": {
    "viewport": "1920x1080",
    "title": "Pricing | Acme"
  }
}

If Loguro.identify() has been called, user_id is also attached.

SPA route changes

Enable with:

<script
  src="https://logu.ro/loguro.js"
  data-api-key="brk_..."
  data-analytics="pageviews,spa"
></script>

(Or data-analytics="all".)

The SDK monkey-patches history.pushState and history.replaceState, and listens to popstate (browser back/forward). On any of those, a new pageview is queued — debounced 150ms to coalesce burst navigations.

Same-path dedup

If pushState or replaceState fires for the same path the user is already on, the SDK ignores it for 30 seconds. Frameworks call replaceState constantly during hydration, scroll restoration, and search-param toggles — counting each as a pageview would inflate path counts 5–10×.

Real reloads after 30s+ still count. A genuine route change always counts. Only ?query=foo flips and other same-path noise are dropped.

Works out of the box with:

  • React Router (useNavigate, <Link>)
  • Next.js App Router (useRouter().push, <Link>)
  • SvelteKit (goto, <a href> with data-sveltekit-preload-data)
  • Vue Router (router.push)
  • Any framework that uses the History API

You don’t need to wire anything up framework-side. The patch on history is universal.

What if my framework uses hashes? (#/about)

popstate fires on hash changes too, so the SDK captures them. path will include the hash (e.g. /#/about).

Manual pageview

For frameworks that don’t use the History API, or for splash-screen / modal flows that should count as a “view”:

window.Loguro.pageview();                                    // current URL + title
window.Loguro.pageview({ url: '/checkout/step-2' });         // override path
window.Loguro.pageview({ url: '/cart', title: 'Your Cart' });

Time on page

Enable with:

data-analytics-time-on-page="true"

Before the next pageview (SPA navigation) or on pagehide, the SDK emits a pageview_end event with the milliseconds since the last pageview started:

{
  "event_type": "pageview_end",
  "path": "/pricing",
  "url": "https://example.com/pricing",
  "visitor_id": "01HZ...",
  "session_id": "01HZ...",
  "timestamp": "2026-05-14T10:01:23.000Z",
  "custom_props": { "duration_ms": 83400 }
}

Use to compute median time on page, bounce rate, etc.

Scroll depth

Enable with:

data-analytics-scroll="true"

The SDK passively listens to scroll events (passive listener — zero perf cost) and tracks the maximum scroll percentage reached. Reported with the pageview_end event:

{
  "event_type": "pageview_end",
  "custom_props": {
    "duration_ms": 83400,
    "max_scroll_pct": 67
  }
}

Useful for content engagement: do readers actually reach the bottom of long-form articles?

Combined recommended setup for a marketing site

<script
  src="https://logu.ro/loguro.js"
  data-api-key="brk_..."
  data-analytics="all"
  data-analytics-time-on-page="true"
  data-analytics-scroll="true"
></script>

With this:

  • Every page load fires a pageview
  • SPA navigations fire a new pageview per route
  • Each page transition or close emits a pageview_end with duration_ms + max_scroll_pct
  • CTAs marked [data-loguro-event] fire automatically
  • Web Vitals (LCP, CLS, INP) report once per page
  • Long tasks > 50ms are logged as warnings
  • console.error and unhandled exceptions still flow into your Loguro logs

Filtering / excluding pages

The SDK doesn’t have a built-in URL exclusion list. If you want to skip tracking on certain routes (admin pages, internal dashboards), wrap your scripts:

<script>
  // Only load Loguro on public pages
  if (!window.location.pathname.startsWith('/admin')) {
    var s = document.createElement('script');
    s.src = 'https://logu.ro/loguro.js';
    s.setAttribute('data-api-key', 'brk_...');
    s.setAttribute('data-analytics', 'all');
    document.head.appendChild(s);
  }
</script>

Alternative: Loguro.setEnabled(false) on routes you don’t want tracked, then re-enable elsewhere.

Next

// related

See also