Skip to content

Overview ​

browserTracingIntegration gives every page its own trace, with one root span that covers the whole page:

  • pageload: the initial load. Starts at the moment the user started navigating (the browser's timeOrigin), not when the SDK loaded.
  • navigation: every SPA route change (pushState/popstate). Each one starts a new trace, the same boundaries Sentry uses.

Anything that happens while that span is open becomes its child:

  • fetch/XHR calls, as http.client spans (the integration registers httpTracingIntegration for you);
  • your own root span()/asyncSpan()/startInactiveSpan() calls;
  • scripts, stylesheets, images, fonts and other resources loaded during the span, as resource.<type> spans. Only resources that went over the network and took at least minResourceDuration (10 ms) get a span, at most maxResourceSpans (50) per root span. Cache hits and near-instant loads say little about where the time went, and on a typical page they are most of the resources.

The root span itself also carries summary data:

  • resource.count, resource.cached, resource.transfer_size — every resource seen during the span, including the ones that didn't get a span of their own;
  • for a pageload, how long each loading phase took (ms), from Navigation Timing: timing.redirect, timing.dns, timing.connect, timing.tls, timing.request, timing.response, timing.dom, timing.dom_content_loaded, timing.load.

Logs and exceptions captured while the span is open are stamped with its trace.id and span.id.

The pageload span also carries web vitals as span metrics: ttfb, fcp, lcp (milliseconds from the start of navigation) and cls (the sum of unexpected layout shifts during the span).

When the span ends ​

There's no explicit "page done" signal, so the span ends when the page goes idle, like Sentry's idle spans:

  • once idleTimeout (1 s) passes with no child span open, the span ends and is trimmed to the end of its last child. The timeout itself isn't counted;
  • a pageload stays open at least until the load event, and never ends before it;
  • finalTimeout (30 s) caps it however busy the page stays;
  • a navigation ends the current span right away and starts the next one;
  • switching to another tab ends the current span with status cancelled.

The reason is stored on the span as idle.finish_reason: idleTimeout, finalTimeout or externalFinish.

Spans started after the root span has ended (a click that fires a request a minute later) still go to the page's trace, but as separate roots.

Walking a visit ​

Each page's trace records the trace of the page the user came from as previous_trace.id (a context on its pageload/navigation root span). The trace page on the dashboard uses it to show Previous page / Next page links, so you can walk through a whole visit one page at a time. Each page stays a short trace with a meaningful duration and is sampled on its own (tracesSampleRate, see Tracing → Sampling).

SPA navigations are linked in memory. To also link across full page loads (a plain <a href>, a form post, a server redirect, location.href = ...), the current page's trace is kept in the tab's sessionStorage under buglapse_previous_trace, and the next page's pageload trace links back to it. linkPreviousTrace controls this:

  • 'session-storage' (default): link SPA navigations and full page loads;
  • 'in-memory': link SPA navigations only, every full page load starts an unlinked trace;
  • 'off': don't link traces.

Limits of the sessionStorage link:

  • a trace that started more than an hour before the next page loads isn't linked;
  • it's per tab and per origin, so a page on another domain (or subdomain) doesn't link back;
  • a link opened in a new tab may copy the tab's sessionStorage, so two pages can link back to the same one. Next page then leads to the one that started first;
  • if the previous page's trace wasn't sampled, the link has nowhere to go and Previous page isn't shown.

Usage ​

ts
import { init } from '@buglapse/browser'
import { browserTracingIntegration } from '@buglapse/browser/integrations'

init({
    dsn: '{dsn}',
    integrations: [browserTracingIntegration()],
})

With the CDN script, turn on Browser tracing in the project's Settings → Loader Script.

Span names default to the URL pathname. Use beforeStartSpan to group pages by route instead, so every /users/42 and /users/43 shows up as one /users/:id:

ts
browserTracingIntegration({
    beforeStartSpan: ({ name }) => ({ name: name.replace(/\/\d+(?=\/|$)/g, '/:id') }),
})

Options ​

OptionTypeDefaultDescription
idleTimeoutnumber1000Ms with no open child span after which the root span ends
finalTimeoutnumber30000Maximum length of a root span, in ms
instrumentPageLoadbooleantrueStart a pageload span for the initial load
instrumentNavigationbooleantrueStart a navigation span (and a new trace) on every SPA navigation
traceRequestsbooleantrueRegister httpTracingIntegration for fetch/XHR spans
traceResourcesbooleantrueRecord resource.* spans from Resource Timing
traceCachedResourcesbooleanfalseAlso record resources served from the browser cache
minResourceDurationnumber10Skip resources that loaded faster than this, in ms
maxResourceSpansnumber50Cap on resource.* spans per root span
beforeStartSpan({ name, op, url }) => { name }—Rename the span before it starts
linkPreviousTrace'session-storage' | 'in-memory' | 'off''session-storage'How a page's trace links to the previous page's, see Walking a visit