Customizing routes

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

BugSnag 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. If you use React Router, Vue Router or Angular routing BugSnag provides additional packages to integrate with these routing libraries and re-use their route resolution. If you have your own routing approach, BugSnag comes with a default behavior that can be customized to suit your app.

React Router

If you are using React Router, you can use the @bugsnag/react-router-performance package to listen for navigation events and map page URLs back to your defined routes.

The package can be installed from the npm registry using npm or yarn:

yarn add @bugsnag/react-router-performance
# or
npm install --save @bugsnag/react-router-performance

To use the package, create a ReactRouterRoutingProvider and provide it as part of your BugSnag configuration with the routes and basename (if not served from the root) from your router configuration:

import BugsnagPerformance from '@bugsnag/browser-performance'
import { ReactRouterRoutingProvider } from '@bugsnag/react-router-performance'

const basename = '/my-app'
const routes = [
    {
        path: '/',
        element: <Root />,
        children: [
          // ...
        ]
    },
]
const router = createBrowserRouter(routes, { basename })

BugsnagPerformance.start({
    apiKey: 'YOUR_API_KEY',
    routingProvider: new ReactRouterRoutingProvider(routes, basename)
})

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <RouterProvider router={router} />
  </React.StrictMode>,
)

Vue Router

If you are using Vue Router, you can use the @bugsnag/vue-router-performance package to listen for navigation events and map page URLs back to your defined routes.

The package can be installed from the npm registry using npm or yarn:

yarn add @bugsnag/vue-router-performance
# or
npm install --save @bugsnag/vue-router-performance

To use the package, create a VueRouterRoutingProvider and provide it as part of your BugSnag configuration with the routes and base (if not served from the root) from your router configuration:

import BugsnagPerformance from '@bugsnag/browser-performance'
import { VueRouterRoutingProvider } from '@bugsnag/vue-router-performance'
import { createRouter, createWebHistory } from 'vue-router'

const base = '/my-app'

const router = createRouter({
  history: createWebHistory(base),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView
    },
    {
      path: '/contacts/:contactId',
      name: 'contact',
      component: () => import('../views/ContactView.vue')
    }
  ]
})

BugsnagPerformance.start({
  apiKey: 'YOUR_API_KEY',
  routingProvider: new VueRouterRoutingProvider(router, base)
})

const app = createApp(App)
app.use(router)
app.mount('#app')

Angular

If you are using Angular routing, you can use the @bugsnag/angular-router-performance package to automatically create route change and full page load spans with the appropriate route name.

The package can be installed from the npm registry using npm or yarn:

yarn add @bugsnag/angular-performance
# or
npm install --save @bugsnag/angular-performance

To use the package, create an AngularRoutingProvider and provide it as part of your BugSnag configuration. In order for the provider to have access to the router, the bugsnagBootstrapper is also required and should be registered as a provider:

import BugsnagPerformance from '@bugsnag/browser-performance';
import { AngularRoutingProvider, bugsnagBootstrapper } from '@bugsnag/angular-performance';

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

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    AppRoutingModule
  ],
  providers: [
    bugsnagBootstrapper,
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

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)
}