Core Web Vitals

Core Web Vitals in SPAs: measure route changes without misleading results

Define the navigation and the measurement boundary before interpreting the result.

Route panels inside a browser window above a timeline with markers
Conceptual illustration of route changes within a persistent browser document.

To evaluate performance in a single page application, measure the initial document load, subsequent route changes and the interactions between them. These are related experiences with different boundaries. A quick response to a click does not prove that the destination has loaded, and an attractive entry page score does not describe every route a visitor opens afterward.

Define what your report actually measures

A full navigation creates a new document. A typical SPA route change updates the current document instead. Changing the address with the History API does not, by itself, define a new performance measurement in every browser and reporting system.

Distinguish document based collection from collection that explicitly supports soft navigations. Support varies across browsers, collectors and report types. Where soft navigation measurements are available, preserve that identity instead of silently merging them with conventional page load results. A custom route timer is another useful signal, but it is not automatically LCP, INP or CLS.

Google's SPA measurement guidance explains why the navigation model matters. Read the definition of the dataset your tool supplies before comparing results. An absent route measurement means missing coverage, not an instant route.

Diagnose the entry route on its own

Open an important destination directly in a fresh session. Watch how the browser discovers the main content, downloads its assets and executes the code needed to display it. Server rendered or prerendered content can make that discovery earlier, but hydration can still add interaction work.

Separate network waiting from execution and rendering. A route can download little and still pause while JavaScript runs. Conversely, reducing script execution will not resolve an API request that starts only after avoidable dependencies finish. Use the LCP guide to investigate the visible content path rather than assuming the framework determines the result.

Give route readiness a useful definition

For a product route, readiness might mean the product identity, current price and primary action are available. For an article route, it might mean the heading and main text are committed. Choose a definition that matches the visitor's task and use it consistently. A spinner disappearing is insufficient if the next action remains unavailable.

Record the start at the router event that represents the user's intent to navigate. Record completion only when the chosen condition is met. Keep cancellations, failures and redirects distinguishable from successful transitions. If a later navigation supersedes an earlier one, do not let the earlier completion masquerade as the new route's result.

Add an application timing signal

This example creates a timer for a completed route transition. Call beginRouteTiming() when navigation starts, then call the returned function once your readiness condition is satisfied. Use a normalized route name, such as article, rather than a URL containing personal information.

javascript
function beginRouteTiming(routeName) {
  const id = crypto.randomUUID();
  const start = `route:${id}:start`;
  const end = `route:${id}:end`;
  const name = `route:${id}:ready`;
  performance.mark(start);

  return function finishRouteTiming() {
    performance.mark(end);
    const result = performance.measure(name, {
      start,
      end,
      detail: { route: routeName }
    });
    performance.clearMarks(start);
    performance.clearMarks(end);
    performance.clearMeasures(name);
    return result.duration;
  };
}

The returned duration uses milliseconds. This helper measures the interval between application signals; it does not verify that pixels have been presented or that the page meets a Core Web Vitals threshold. Connect it to the router and rendering lifecycle you actually use. Add cancellation cleanup and error reporting in that integration. The unique names keep simultaneous successful measurements separate, while the cleanup avoids retaining their timeline entries.

A framework commit or a requestAnimationFrame callback is not proof that all useful content has appeared. Compare the timing signals with a browser trace and the visible behavior before deciding what the metric means.

Separate responsiveness from destination loading

INP concerns the response to user interactions through the next paint. A click can quickly show a loading state while the destination remains unavailable. That can be a responsive interaction and a slow route at the same time. Preserve both observations instead of renaming the whole route duration INP.

Inspect expensive event handlers, synchronous rendering and repeated layout work. Defer work that does not contribute to the immediate response, and keep the visible progress state honest. If the main thread is busy after the click, inspect the trace before changing network settings. The INP guide explains how to narrow that diagnosis.

Load what the route needs without destabilizing it

Splitting code by route can reduce unnecessary entry work, but an extra sequence of dependent requests can delay the destination. Start independent data and code work together when the architecture permits it. Prefetch selectively; speculative work can compete with the route the visitor is using.

Reserve space for images and asynchronous panels. Maintain stable navigation and avoid inserting late content above what the visitor is reading. A transition can contain distracting movement even when a particular shift is excluded from CLS because of recent input. Use the visible experience as well as the metric when deciding whether a transition works.

Compare equivalent journeys and datasets

Test direct entry, internal navigation, repeated navigation, back and forward behavior, and a transition during slow data delivery. Include a failed request and an abandoned transition. Preserve the cache state and device conditions when comparing changes. Otherwise a warm route can appear to prove an improvement that came only from earlier work.

The good boundaries are LCP at or below 2.5 s, INP at or below 200 ms and CLS at or below 0.1, assessed at the 75th percentile with mobile and desktop separated. They apply to the defined Core Web Vitals measurements, not to arbitrary application timers. Check whether a report represents a URL, an origin, document loads or identified soft navigations.

Keep route context close to the interaction or transition that produced it, rather than attaching whichever URL is current when telemetry is sent. Review the difference between field and lab data before treating a successful local trace as evidence about all visitors. A useful performance report makes slow journeys identifiable and fixes testable.

Related guides