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.
One document can contain several page experiences.
Inside this Insight
What the article follows
Written by
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.
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.
The user opens /dashboard
The user moves to /reports
The user moves to /billing
Route principle
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.
| Situation | Likely interpretation |
|---|---|
User opens /reports, the URL changes, and the main content renders | Soft navigation candidate |
| User opens a dropdown | Normal interaction |
| A filter changes with no route change | Usually an interaction, not automatically a navigation |
| A client-side router changes the URL and replaces the page view | Soft navigation candidate |
| The browser loads a completely new document | Hard 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.
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.
if (PerformanceObserver.supportedEntryTypes.includes("soft-navigation")) {const observer = new PerformanceObserver((list) => { console.log(list.getEntries());}); observer.observe({type: "soft-navigation",buffered: true,});}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:
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.
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 });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.
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.
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,};}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.
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.
navigate
soft-navigation
routing-apis
Decision point
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:
| Baseline | What it represents |
|---|---|
| Historical hard-navigation baseline | Full document loads and older cross-browser reporting |
| New route-level baseline | SPA 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.
| Signal | What to inspect |
|---|---|
| Slow route LCP | Data loading, route bundle, image load, large render work |
| Poor route INP | Synchronous click handlers, parsing, rendering, main-thread work |
| High route CLS | Missing dimensions, late content, unstable component layout |
| Good hard load, poor soft route | Client-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.
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
Add reportSoftNavs separately
Store navigationURL and navigationType
Mark the new analytics baseline
Segment unsupported browsers
Investigate by route
Before moving on
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.
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.
- 01Source
Chrome for Developers
Official sourceJul 28, 2026New in Chrome 151
Used for the Chrome 151 release of the soft-navigation and interaction-contentful-paint performance entry types.
- 02Source
Chrome for Developers
DocumentationJul 21, 2026Measuring soft navigations
Used for soft-navigation detection criteria, navigation attribution, LCP behaviour, INP and CLS boundaries, TTFB handling, and dual-measurement guidance.
- 03Source
web.dev
DocumentationAug 11, 2026How SPA architectures affect Core Web Vitals
Used for the current CrUX integration status, browser-support caveat, and SPA route-transition context.
- 04Source
GoogleChrome / web-vitals
DocumentationJul 21, 2026web-vitals changelog
Used for the v6.0.0 release date and the addition of soft-navigation support.
- 05Source
GoogleChrome / web-vitals
Documentationweb-vitals documentation
Used for reportSoftNavs, dual callback reporting, navigationURL, navigationId, navigationType, and metric behaviour across soft navigations.
- 06Source
Cloudflare
Official sourceAug 21, 2026Web 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.
- 07Source
Cloudflare Web Analytics
DocumentationAug 20, 2026Web Analytics for Single Page Applications (SPAs)
Used for the current SPA detection order: Soft Navigation API, Navigation API, then History API fallback.
Source links support the facts they are attached to. They do not imply that the source publisher endorses Ascent's interpretation or recommendations.
