Tracking SPA Navigation Latency with Dynatrace RUM Custom Actions
Learn how to use Dynatrace RUM’s custom JavaScript action API to measure SPA navigation latency, verify the results, and manage the trade‑offs of added overhead.
14 Jul 2026, 16:13 UTC

Problem: SPA navigation hides real user latency
Single‑page applications (SPAs) update the DOM without a full page reload, so Dynatrace’s default Real User Monitoring (RUM) beacon only fires on the initial load. Subsequent route changes are invisible to RUM, which means you lose visibility into the user‑perceived latency that matters most after the first paint.
Thesis: Use the Dynatrace RUM dt object’s action API to create custom timers that align with route transitions
By calling dt.action('start', name) when a route begins and dt.action('stop', name) when it ends, you emit a custom action beacon that Dynatrace treats like a built‑in user action. The resulting waterfall chart shows a bar for each navigation, letting you measure and alert on SPA‑specific latency.
How Dynatrace RUM captures custom actions
When the RUM agent loads, it exposes a global dt object. The action method accepts three parameters:
type– either'start'or'stop'.name– a string that appears as the action label in the UI (max 200 characters, no reserved words).attributes(optional) – key‑value pairs you can later filter on.
The agent automatically calculates the elapsed time between the matching start and stop calls and sends a beacon with that duration.
Worked example: React wrapper for route‑based actions
The following snippet shows a small higher‑order component that wraps any route‑rendering component. It assumes the Dynatrace RUM agent is already loaded (the dt global exists).
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
/**
* Wraps a component to emit Dynatrace custom actions on route changes.
* @param {React.ComponentType} WrappedComponent - The component to render for the route.
* @returns {React.ComponentType} Enhanced component.
*/
export function withDynatraceRouteAction(WrappedComponent) {
return function Enhanced(props) {
const location = useLocation();
// Use the pathname as the action name; you could also include query params.
const actionName = `SPA-${location.pathname.replace(/\//g, '-')}`;
useEffect(() => {
// Guard against missing dt (e.g., in non‑browser environments).
if (typeof window?.dt === 'undefined') return;
// Start the timer as soon as the route mounts.
window.dt.action('start', actionName);
// Cleanup: stop the timer when the component unmounts or before the next route.
return () => {
window.dt.action('stop', actionName);
};
}, [location, actionName]);
return ;
};
}
// Usage in your router:
//
Explanation:
- The effect runs on every location change.
dt.action('start', …)marks the beginning of the navigation.- The cleanup function runs before the next effect (or on unmount) and calls
dt.action('stop', …), completing the timer. - Using the pathname ensures the action name stays within the 200‑character limit and avoids reserved words.
Verifying the instrumentation
- Open a user session in Dynatrace (Applications > {your app} > User sessions).
- Select a session that includes SPA navigation.
- Navigate to the Waterfall tab.
- You should see a custom action bar labeled with the
actionNameyou defined (e.g.,SPA-dashboard). - Hover over the bar to view the reported duration.
- For a sanity check, open the browser console and manually measure the same navigation with
performance.now()before and after the route change; the numbers should be close (within a few milliseconds of measurement error). - Ensure rapid successive route changes do not produce duplicate bars; if they do, add a simple debounce or check for an existing active action before starting a new one.
Trade‑offs and limitations
Adding custom actions introduces extra JavaScript overhead:
- Each start/stop pair adds a small beacon payload; on low‑end devices many rapid actions can increase CPU usage and delay UI updates.
- Dynatrace licenses are based on the number of distinct user actions; creating thousands of unique action names can consume your license quota and make charts harder to read.
- The agent enforces a 200‑character limit on action names and blocks certain reserved words (e.g.,
action,error). Violating these rules drops the event silently.
Practical mitigation:
- Limit custom actions to meaningful navigation steps (e.g., top‑level routes) rather than every component mount.
- Use a naming convention that groups similar routes (e.g.,
SPA-product-*) to keep the cardinality low. - Monitor beacon size in the browser’s Network tab; ensure each RUM payload stays under a few kilobytes.
Actionable closing
Instrumenting SPA navigation with Dynatrace RUM’s custom action API gives you the latency visibility you need without overhauling your monitoring stack. Start by wrapping your top‑level routes, verify the waterfall bars, and then tune the naming granularity to balance insight with overhead. Once the custom actions are flowing, you can create alerts on action duration, build dashboards that compare SPA versus traditional page‑load performance, and feed the data into your existing SLO calculations.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.