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.
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'
import { createRoot } from 'react-dom/client'
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)
})
const root = createRoot(document.getElementById('root'))
root.render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>,
)
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')
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 { }
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).
DefaultRoutingProvider
for other routing librariesIt 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)
}
}
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.
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:
PerformanceNavigationTiming
)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)
}