BugSnag is now Insight Hub – we're making some changes to how the product looks, but this won't impact the way you use BugSnag or any of your integrations.

Customizing routes

Find out how to tailor your BugSnag integration to match your app’s page routing style.

The BugSnag SDK records the time it takes to load a page in your web app. However in most dynamic web apps, a “page” is not a straightforward thing to define and nor is identifying the point at which it has finished loading.

Automatic routing packages

If you use one of the popular routing libraries shown below, you can install an additional package to integrate with these routing libraries and re-use their route resolution.

Custom routing

If you use a different router, and have parameterized page URLs, you should group all measurements on your BugSnag dashboard by the page template, or “route”, rather than specific URL on which it was captured. For example, rather than capture /account/john.smith/orders you would want the dashboard to show /account/:username/orders.

To do this, you implement a function that maps a page URL to a generic route name for the DefaultRoutingProvider to use. This is configured in BugSnag as follows:

import BugsnagPerformance, { DefaultRoutingProvider }
  from '@bugsnag/browser-performance'

function resolveRoute (url: URL): string {
  if (url.pathname.match(/\/account\/.*\/orders/i)) {
    return '/account/:username/orders'
  }
  return url.pathname
}

BugsnagPerformance.start({
  apiKey: 'YOUR_API_KEY',
  routingProvider: new DefaultRoutingProvider(resolveRoute)
})

If your routeResolver doesn’t return a non-empty string, the full path for the route is used (taken from url.pathname or “/” if empty).

Extending the DefaultRoutingProvider for other routing libraries

It is common for routing libraries to utilize a nested route structure. The following is an example of flattening a collection of nested routes and extending the DefaultRoutingProvider:

import { DefaultRoutingProvider, StartRouteChangeCallback } from '@bugsnag/browser-performance'
import { pathToRegexp } from 'path-to-regexp'

export interface RouteObject {
  path?: string
  children?: RouteObject[]
}

function flattenRoutes (routes: RouteObject[], _prefix: string = ''): string[] {
  const prefix = `${!_prefix || _prefix === '/' ? _prefix : `${_prefix}/`}`
  return [
    ...routes.map(route => `${prefix}${route.path || ''}`),
    ...routes.reduce<string[]>(
      (accum, route) => [
        ...accum,
        ...(route.children ? flattenRoutes(route.children, `${prefix}${route.path}`) : [])
      ], []
    )
  ]
}

class NestedRoutesRoutingProvider extends DefaultRoutingProvider {
  constructor (routes: RouteObject[], basename?: string) {
    const allRoutes = flattenRoutes(routes)
    function resolveRoute (url: URL): string {
      return allRoutes.find((fullRoutePath) => 
        url.pathname
          .replace(basename ?? '', '')
          .match(pathToRegexp(fullRoutePath))
      ) || 'no-route-found'
    }
    super(resolveRoute)
  }
}

Listening to route changes

In order to detect route changes that are not full page loads, as is common in single-page apps (SPAs), our router library integrations (see above) listen for navigation events within the API of these libraries.

For apps not using these router integrations, BugSnag’s default behavior is to listen to calls to pushState, and popstate events. This behavior can be customized by providing your own RoutingProvider implementation to trigger the start and end of a navigation event:

import BugsnagPerformance, { RoutingProvider, StartRouteChangeCallback }
  from '@bugsnag/browser-performance'

class MyCustomRoutingProvider implements RoutingProvider {
  listenForRouteChanges (startRouteChangeSpan: StartRouteChangeCallback) {
    // Setup a callback for a navigation event starting
    SomeNavigationLibrary.addNavigationStartListener(() => {
      const url = new URL(location.href)

      // Call the BugSnag callback to generate a span
      const span = startRouteChangeSpan(url)

      // Setup a callback to end the span when the navigation ends
      SomeNavigationLibrary.addNavigationEndListener(() => { 
        span.end()
      }
    }
  }

  resolveRoute (url: URL): string {
    // Implement logic to convert the URL into a generic route name
    // (This method will be called by the startRouteChangeSpan callback.)
  }
}

BugsnagPerformance.start({
  apiKey: 'YOUR_API_KEY',
  routingProvider: new MyCustomRoutingProvider()
})

If your routing library doesn’t provide a suitable API for determining the end of a navigation event, including the rendering of the DOM, you can import onSettle in the BugSnag SDK to use our “settling” heuristics – see below.

Identifying the end of a page load

There is no built-in API available to tell BugSnag when a page – either a full page load or a route change – has finished loading. Instead, we have implemented a listener that uses the following algorithm to fire a callback when all the following are true:

  • the Performance API has signalled the page has loaded (via PerformanceNavigationTiming)
  • there have been no network requests (XHR or fetch) for 100ms
  • there have been no DOM mutations for 100ms

This listener is used for all Page Load calculation and is also exported as onSettle from the BugSnag SDK so that it can be used in RoutingProvider implementations to end the span at the correct time:

onSettle((endTime) => {
  span.end(endTime)
}