Skip to content
Product Insight

How to Measure Core Web Vitals Across SPA Route Changes

A fast first load can hide slow client-side routes. Chrome 151 gives RUM tooling a better way to measure LCP, INP, and CLS as users move through an SPA.

SaaS EngineeringSPA PerformanceCore Web Vitals
Continue to the analysis
Route-level performance

One document can contain several page experiences.

Inside this Insight

What the article follows

1Hard load: /dashboard
2Soft route: /reports
3Soft route: /billing
SaaS Engineering

Written by

Shruti SaraswatAscent Innovate Software
Published
SaaS Engineering
Route-level performance

The first load can look fast while the product feels slow after login.

A user signs into a SaaS product and lands on the dashboard. The dashboard appears quickly, so the initial page-load metrics look healthy.

Then the user clicks Reports. There is no full browser refresh. The URL changes, the application fetches data, a large table appears, a chart renders a moment later, and part of the layout shifts while everything settles. That transition takes three seconds.

From the user's point of view, they just opened another page. For traditional page-load measurement, the browser may still be inside the same document that originally loaded the dashboard.

Chrome 151 changes that measurement boundary in a useful way. It introduced soft-navigation and interaction-contentful-paint performance entries for interaction-driven SPA transitions. Google's SPA guidance was updated around the new APIs, and Cloudflare began rolling out improved soft-navigation measurement in Web Analytics on August 21.

The first page load is only one part of SPA performance

A traditional multi-page site gives the browser a clear boundary each time the user navigates. A new document loads, the browser knows a navigation occurred, and performance metrics belong naturally to that page load.

An SPA can behave differently. A user may spend twenty minutes inside one browser document while moving through /dashboard, /customers, /reports, /billing, and /settings. JavaScript updates the URL and visible content without unloading the document.

For the user, those routes feel like separate pages. Historically, Core Web Vitals were much easier to associate with the original document load than with each later route transition. Google's SPA guidance describes this long-running limitation directly.

Why can the first page load look fast while the app still feels slow?

Later route changes can introduce their own network requests, JavaScript work, rendering delays, images, layout movement, and slow interactions even when the original document loaded quickly.

A dashboard might appear in under a second while moving to /reports triggers a large API request and expensive chart rendering. A dashboard focused only on the initial load can therefore tell a much healthier story than the user would.

One document, several experiences

A long-lived SPA can cross several performance boundaries without reloading the document.

Treat each page-like route transition as another user experience when the browser and measurement tooling can identify that boundary.

01Hard navigation

The user opens /dashboard

The browser loads the document and traditional LCP, INP, and CLS measurement begins from the normal page-navigation boundary.
02Soft navigation

The user moves to /reports

The document stays alive while the URL and main content change. Route-level data fetches, paints, interactions, and layout shifts now belong to the Reports experience.
03Soft navigation

The user moves to /billing

Another client-side route change creates another measurement boundary without requiring a full document reload.

Route principle

The browser document may stay alive while the user's page experience changes several times.

What exactly counts as a soft navigation?

Chrome does not treat every DOM update as a new navigation. Its current definition requires three things: a user action starts the transition, the visible URL changes, and the interaction results in a visible paint.

That gives the browser a framework-independent boundary. It does not need to know whether the application uses React Router, Next.js, Vue Router, Angular, Svelte, or a custom routing layer.

SituationLikely interpretation
User opens /reports, the URL changes, and the main content rendersSoft navigation candidate
User opens a dropdownNormal interaction
A filter changes with no route changeUsually an interaction, not automatically a navigation
A client-side router changes the URL and replaces the page viewSoft navigation candidate
The browser loads a completely new documentHard navigation

Is every client-side UI change a soft navigation?

No. A soft navigation represents a page-like transition inside the current document, not every state change that happens in an SPA.

This distinction matters because route-level Core Web Vitals would become noisy if every modal, filter, accordion, or local state update started a fresh page measurement. Chrome's definition gives measurement tools a shared signal for transitions that look more like navigation than ordinary interaction.

Native browser signal

Observe the soft-navigation entries Chrome exposes.

Feature-detect the entry type before observing it. The buffered option also returns soft navigations that occurred before the observer was registered.

observe-soft-navigation.ts
typescript
if (PerformanceObserver.supportedEntryTypes.includes("soft-navigation")) {const observer = new PerformanceObserver((list) => {  console.log(list.getEntries());}); observer.observe({type: "soft-navigation",buffered: true,});}
For production Core Web Vitals reporting, Chrome recommends using a measurement library such as `web-vitals` rather than rebuilding all of the route attribution and metric-reset logic yourself.

Do Core Web Vitals include SPA route changes now?

Chrome 151 now provides browser APIs that make route-level Core Web Vitals measurement possible in supported Chromium environments. The web-vitals library added soft-navigation support in version 6.0.0 on July 21, 2026, and RUM products are beginning to use the new signals.

That does not mean every Core Web Vitals dataset already treats SPA route changes as separate page loads.

Google's updated SPA guidance says Chrome has introduced the APIs, but no timeline has yet been published for integrating soft-navigation measurements into the Chrome User Experience Report, or CrUX. Other browser engines also do not currently expose the same native soft-navigation APIs.

A route change needs its own performance boundary

When Chrome detects a soft navigation, it emits a soft-navigation performance entry. That entry carries the new URL in its name, a unique navigationId, and the interactionId for the interaction that started the transition.

For a RUM system, the operating model is straightforward: finalize the previous route's metrics, report them against the previous URL, and start a new measurement window for the new route.

If a user moves from /dashboard to /reports, the metrics accumulated for the dashboard should finish against /dashboard. The new soft-navigation entry then establishes the context for /reports.

That sounds simple, but it changes what a long-lived SPA session can tell you. Instead of one performance record covering everything the user did after login, the data can begin showing which route actually became slow.

How do I measure LCP after a client-side route change?

For the initial hard navigation, LCP continues to use the normal largest-contentful-paint entries. Soft navigation needs another signal because the document itself did not start over.

Chrome 151 introduced interaction-contentful-paint. These entries report contentful paints inside regions modified by a user interaction, including asynchronous updates that complete after a fetch() request. A detected soft navigation can use the largest qualifying interaction-driven paint as the route-level LCP.

There is an important consequence: the LCP element for a route can differ depending on how the user reached that route.

Suppose /reports contains a large application header that remains mounted while the main report content changes. If the user loads /reports directly, that header is newly painted and may qualify as LCP. If the user moves from /dashboard to /reports, the header may already be present and is not repainted as part of the route change. A newly rendered chart or table can become the soft-navigation LCP instead.

The direct page load and the client-side transition are different user journeys, so different LCP elements are not automatically a measurement problem.

Should INP and CLS restart after a soft navigation?

For route-level RUM analysis, yes. Finalize the previous route's INP and CLS when the next soft navigation begins, then start fresh metric state for the new route.

Chrome's soft-navigation boundary gives tooling a standardized place to slice a long-lived SPA session. The web-vitals implementation resets INP to interactions after the soft navigation and resets CLS so layout shifts can be measured separately for the next route.

This makes diagnosis much more useful. If /billing has poor INP while /dashboard stays responsive, the team can investigate the Billing route instead of looking at one session-wide interaction value that mixes several screens together.

What does TTFB mean when no new document was requested?

TTFB becomes awkward for a soft navigation because there may be no equivalent of the original document request. A route transition might fetch fresh data, use prefetched data, read data already in memory, or perform no network request at all.

Choosing one request and calling it "time to first byte" would give the metric a meaning it does not reliably have. Chrome currently recommends reporting TTFB as 0 for soft navigations, and web-vitals follows that approach.

This is a useful reminder that the same metric name can need different interpretation across hard and soft navigation types.

Use web-vitals v6 instead of rebuilding every edge case yourself

GoogleChrome's web-vitals library added soft-navigation support in version 6.0.0. The library handles route attribution, metric resets, navigation IDs, navigation URLs, and timing details that are easy to get wrong in a custom implementation.

A production setup can keep its traditional measurement stream while adding a separate route-level stream for the Core Web Vitals:

Recommended production path

Keep traditional reporting and add a soft-navigation stream beside it.

The `reportSoftNavs` option changes the metric lifecycle in browsers that support soft navigations, so registering both callback sets preserves the traditional view for comparison.

web-vitals.ts
typescript
import { onCLS, onINP, onLCP } from "web-vitals"; onCLS(sendTraditional);onINP(sendTraditional);onLCP(sendTraditional); onCLS(sendSoftNavigation, { reportSoftNavs: true });onINP(sendSoftNavigation, { reportSoftNavs: true });onLCP(sendSoftNavigation, { reportSoftNavs: true });
In both streams, use the metric's `navigationURL` rather than assuming the current browser URL is the route the metric belongs to. Soft-navigation metrics can be reported after the user has already moved again.

Should I replace my existing Core Web Vitals reporting?

No. Add soft-navigation reporting alongside the existing hard-navigation view for now.

Keeping both preserves historical comparisons, keeps a cross-browser view while native support remains Chromium-only, and lets the team learn how hard loads and route transitions differ before changing dashboards or alerts around the new data.

Keep navigation context in the RUM event

A route-level performance record should tell you how the user reached the route, not only which URL was visible when the metric arrived.

A deep-linked /reports load and a /dashboard → /reports client-side transition can involve different network, rendering, and LCP paths. Storing that context makes the data much easier to interpret.

Route-level RUM

Store the metric with the route and navigation context that produced it.

`web-vitals` exposes `navigationURL`, `navigationId`, and `navigationType` on the metric object. Keep those fields with the measurement instead of trying to reconstruct the route later.

route-vitals.ts
typescript
type CoreWebVitalMetric = {name: "LCP" | "INP" | "CLS";value: number;navigationURL?: string;navigationId: number;navigationType: string;}; type RouteVital = {metric: "LCP" | "INP" | "CLS";value: number;navigationURL: string;navigationId: number;navigationType: string;appVersion: string;}; function toRouteVital(metric: CoreWebVitalMetric): RouteVital {return {metric: metric.name,value: metric.value,navigationURL: metric.navigationURL ?? window.location.href,navigationId: metric.navigationId,navigationType: metric.navigationType,appVersion: APP_VERSION,};}
Keep user-identifying data out of this event unless the product has a separate, justified analytics requirement. Route, release version, navigation context, and metric values are usually enough for performance diagnosis.

Chrome also recommends retaining navigation type when comparing soft-navigation LCP, INP, and CLS because route transitions can have different characteristics from full page loads.

Why did my Cloudflare pageviews change?

Cloudflare began rolling out improved SPA soft-navigation measurement on August 21. The company explicitly warns that pageview volume in the dashboard and GraphQL API may change depending on front-end architecture and visitor traffic patterns.

Before this update, Cloudflare relied on History API behaviour and grouped client-side navigations differently. Its current Web Analytics reporting now distinguishes three navigation paths.

Cloudflare navigation types

The same SPA can now produce different navigationType values depending on how the route was measured.

Keep these paths separate when interpreting route-level analytics because the native soft-navigation path can provide measurement that the fallback path cannot.

01

navigate

A traditional hard navigation where the browser loads a new document.
Use this as the familiar page-load baseline.
02

soft-navigation

A client-side route transition detected with Chrome's native Soft Navigation API.
Cloudflare can collect native soft-navigation LCP on this path.
03

routing-apis

A client-side route transition detected through Navigation API or History API fallback when the native Soft Navigation API is unavailable.
Cloudflare reports the other Core Web Vitals here, but not soft-navigation LCP.

Decision point

A change in measured pageviews can come from a change in navigation detection, not only from a change in traffic.

Did my traffic suddenly increase?

Possibly, but a higher pageview count after the measurement update does not prove that traffic increased.

If client-side route transitions are now being recorded as separate pageviews more accurately, the analytics definition has changed. Annotate the rollout date and establish a fresh baseline before treating the higher count as product growth.

That small operational step can prevent a measurement change from becoming a misleading business conclusion.

What happens in Safari and Firefox?

The native Soft Navigation API is currently a Chromium capability. Cloudflare handles that gap by falling back to Navigation API events and then History API behaviour when the native signal is unavailable.

The fallback still gives teams useful SPA navigation information, but Cloudflare's current documentation says soft-navigation LCP cannot be collected for its routing-apis fallback path. The other Core Web Vitals remain available there.

That creates a simple reporting rule: do not merge every navigation path into one number without preserving how the measurement was produced.

A Chrome soft-navigation event and a Safari or Firefox fallback event are not identical measurement paths. Keeping navigationType visible prevents those differences from disappearing inside an average.

Is this already part of CrUX and Google Search ranking?

As of August 24, 2026, Chrome provides the native APIs for measuring Core Web Vitals across SPA route transitions, but Google has not published a timeline for including these soft-navigation measurements in CrUX. Other browser engines also do not currently support the same APIs.

So teams should not assume that every client-side SPA route is already being evaluated as a separate Core Web Vitals page experience in Google's public field dataset.

The useful path today is to use route-level RUM because it gives you a clearer view of your own users, keep traditional hard-navigation data for historical and cross-browser comparison, and watch CrUX support as a separate platform change.

This section is intentionally date-sensitive. The route-level measurement architecture remains useful when CrUX support eventually changes.

Do not compare a new route-level baseline with old dashboards as if nothing changed

Measurement changes can create false conclusions when the before-and-after data represents different things.

If a product starts recording /dashboard → /reports as its own measured experience in August, comparing September route-level LCP directly with July's document-level LCP can mix two different measurement models.

A cleaner rollout keeps two baselines visible:

BaselineWhat it represents
Historical hard-navigation baselineFull document loads and older cross-browser reporting
New route-level baselineSPA soft navigations segmented by navigation type

Versioning or annotating the RUM schema helps too. If measurement logic changes later, the team can separate old and new records instead of wondering why a route appears to improve or regress overnight.

What should you investigate when one SPA route is slow?

Route-level measurement becomes useful when it leads to a smaller, concrete engineering investigation.

Suppose /reports has poor route-level LCP and INP while /dashboard is healthy. The route itself gives the team a much better place to start.

SignalWhat to inspect
Slow route LCPData loading, route bundle, image load, large render work
Poor route INPSynchronous click handlers, parsing, rendering, main-thread work
High route CLSMissing dimensions, late content, unstable component layout
Good hard load, poor soft routeClient-side navigation path rather than initial server delivery

A server-rendering benchmark can tell you a lot about the initial response path. It does not automatically tell you how a signed-in user experiences route changes twenty minutes later.

That is why this Insight complements our earlier Next.js vs. React Router in 2026: An SSR Performance Test. The SSR comparison looks at server-rendering behaviour. Route-level RUM looks at what happens as the user continues through the application.

When route-level performance exposes slow rendering, repeated client-side work, layout instability, or difficult-to-trace behaviour, it can also give Software Fixes, Performance & Scaling work a much clearer starting point.

Rollout checklist

Add route-level measurement without throwing away the baseline you already trust.

The goal is not to replace every performance dashboard overnight. Add the new route boundary in a way that keeps historical and cross-browser comparisons understandable.

Keep traditional Core Web Vitals reporting

Preserve the existing hard-navigation stream for historical trends and browsers without native soft-navigation support.

Add reportSoftNavs separately

Start learning from route-level LCP, INP, and CLS without mixing the new lifecycle silently into the old dashboard.

Store navigationURL and navigationType

Keep the route and navigation path attached to the metric so hard loads and client-side transitions can be compared correctly.

Mark the new analytics baseline

Annotate when route measurement changed so pageview and performance shifts are not mistaken for product changes.

Segment unsupported browsers

Keep Chromium native soft-navigation data distinct from fallback measurement where the available metrics differ.

Investigate by route

Use route-level regressions to narrow the engineering work to data loading, rendering, JavaScript, images, or layout behaviour on the affected screen.

Before moving on

Measure the journey more precisely without pretending the new data is identical to the historical page-load data it sits beside.

A production SPA should measure the journey, not only the entrance

The first page load still matters. It is where the browser creates the document, where many users form their first impression, and where traditional Core Web Vitals remain most established.

A long-lived SaaS session contains much more than that entrance. A user may move through five, ten, or twenty route-level experiences without another full document navigation.

Chrome 151 finally gives measurement tools a standardized browser signal for many of those transitions. web-vitals v6 can use that signal today, and Cloudflare has already started incorporating it into Web Analytics.

For teams running SPAs, the sensible path is measured rather than dramatic: keep existing Core Web Vitals reporting, add a separate soft-navigation stream, preserve navigation context, establish a new route-level baseline, and investigate performance by the routes users actually move through.

That gives you a much more useful picture than asking whether the first page happened to load quickly.

It lets you see where the product becomes slow after the user has already decided to keep using it.

Sources

References used for this Insight

This Insight uses current first-party Chrome, Google, web-vitals, and Cloudflare documentation for soft-navigation APIs, Core Web Vitals behaviour, browser support, and SPA analytics changes. Product interpretation and rollout guidance are Ascent Innovate Software's analysis.

  1. 01

    Chrome for Developers

    Official sourceJul 28, 2026

    New in Chrome 151

    Used for the Chrome 151 release of the soft-navigation and interaction-contentful-paint performance entry types.

    Source
  2. 02

    Chrome for Developers

    DocumentationJul 21, 2026

    Measuring soft navigations

    Used for soft-navigation detection criteria, navigation attribution, LCP behaviour, INP and CLS boundaries, TTFB handling, and dual-measurement guidance.

    Source
  3. 03

    web.dev

    DocumentationAug 11, 2026

    How SPA architectures affect Core Web Vitals

    Used for the current CrUX integration status, browser-support caveat, and SPA route-transition context.

    Source
  4. 04

    GoogleChrome / web-vitals

    DocumentationJul 21, 2026

    web-vitals changelog

    Used for the v6.0.0 release date and the addition of soft-navigation support.

    Source
  5. 05

    GoogleChrome / web-vitals

    Documentation

    web-vitals documentation

    Used for reportSoftNavs, dual callback reporting, navigationURL, navigationId, navigationType, and metric behaviour across soft navigations.

    Source
  6. 06

    Cloudflare

    Official sourceAug 21, 2026

    Web Analytics improves soft navigation measurement for Single Page Applications (SPAs)

    Used for Cloudflare's rollout, pageview-volume warning, navigationType values, and native-versus-fallback LCP behaviour.

    Source
  7. 07

    Cloudflare Web Analytics

    DocumentationAug 20, 2026

    Web Analytics for Single Page Applications (SPAs)

    Used for the current SPA detection order: Soft Navigation API, Navigation API, then History API fallback.

    Source

Source links support the facts they are attached to. They do not imply that the source publisher endorses Ascent's interpretation or recommendations.