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'stimeOrigin), 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.clientspans (the integration registershttpTracingIntegrationfor 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 leastminResourceDuration(10 ms) get a span, at mostmaxResourceSpans(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
loadevent, 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
| Option | Type | Default | Description |
|---|---|---|---|
idleTimeout | number | 1000 | Ms with no open child span after which the root span ends |
finalTimeout | number | 30000 | Maximum length of a root span, in ms |
instrumentPageLoad | boolean | true | Start a pageload span for the initial load |
instrumentNavigation | boolean | true | Start a navigation span (and a new trace) on every SPA navigation |
traceRequests | boolean | true | Register httpTracingIntegration for fetch/XHR spans |
traceResources | boolean | true | Record resource.* spans from Resource Timing |
traceCachedResources | boolean | false | Also record resources served from the browser cache |
minResourceDuration | number | 10 | Skip resources that loaded faster than this, in ms |
maxResourceSpans | number | 50 | Cap 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 |