How Do You Measure Core Web Vitals in a Single Page App?
How do you measure Core Web Vitals in a single page app?
As of September 2026, with the Soft Navigations API, which Chrome enables by default from Chrome 151. It gives a single page app something it never had: a way to say where one page ended and the next began, so Largest Contentful Paint, Cumulative Layout Shift and Interaction to Next Paint can be measured per route instead of per tab.
This has been the quiet embarrassment of single page apps for years. A framework site would post a beautiful LCP number, because the number only ever described the very first load. Every route change after that was invisible. The slowest part of the experience was the part nobody measured.
Here is what actually changed, how the measurement works now, and what we would do about it on a real site.
What is a soft navigation?
Chrome's documentation defines it with three conditions, and all three have to hold. The navigation is initiated by a user action, it results in a visible URL change, and it produces a visible paint.
That triple test is doing real work. It rules out a background fetch that swaps some text, because no user started it. It rules out a modal opening, because the URL did not change. It rules out a route change that renders nothing new, because there is no paint. What is left is close to what a person would call going to another page.
Chrome's own description of the underlying problem is blunt. Rather than loading distinct, individual web pages as the user navigates about the site, these web applications use so-called soft navigations, where the page content is instead changed by JavaScript. Every metric built for the old model quietly stopped describing reality.
Which browsers support this?
Chrome and Edge from version 151. Chrome's documentation states that the soft navigations feature is enabled by default from Chrome 151, and lists support as Chrome 151 and above and Edge 151 and above. Firefox and Safari do not support it.
That is a Chromium-only measurement story, and it is worth saying plainly rather than glossing over. You are not measuring all your users. You are measuring the Chromium share of them, which for most B2B products is the majority but never all.
We think that is still enormously better than the alternative, which was measuring one route out of every session and calling it the site.
How do you detect whether it is available?
Feature detect against the entry types the browser supports. Chrome's documentation checks whether PerformanceObserver.supportedEntryTypes includes the string soft-navigation, and only sets up monitoring if it does.
Do that before anything else, because the alternative is a thrown error on every non-Chromium browser and a broken analytics bundle. It is two lines and it is not optional.
If you already use Google's web-vitals library, you are most of the way there. Soft navigation support was developed on a separate branch and has since been merged into the main library, so recent versions handle the entry types for you rather than leaving you to wire up observers by hand.
How does LCP work for a soft navigation?
Differently, and this is the part that surprises people. LCP for a soft navigation does not come from largest-contentful-paint entries. Chrome introduced a separate entry type, interaction-contentful-paint, and exposes a getLargestInteractionContentfulPaint function to retrieve the largest paint for each navigation.
The reason is structural. The original LCP definition finalises once, on first user interaction, because that is how you stop a metric drifting forever on a long lived page. A single page app is one long lived page by definition, so the old rule would freeze LCP at the first route and never move again. A separate entry type with its own lifecycle is the only clean way out.
The practical consequence: if you are reading largest-contentful-paint entries in a custom script and expecting route level numbers, you will get nothing and conclude the API is broken. It is not. You are reading the wrong stream.
What happens to CLS and INP?
They reset at each soft navigation boundary rather than accumulating across the whole page lifetime. That is the change that makes both metrics usable in a single page app for the first time.
Cumulative Layout Shift was the worse of the two. On a traditional site it accumulates over one page and stops. On a single page app it accumulated over an entire session, which meant a user who visited nine routes carried the layout shift of all nine into one score. Long sessions looked catastrophic and short ones looked clean, and neither told you anything about a specific screen. Per navigation resets fix that outright, and we covered why the metric is so sensitive to this in our piece on what Cumulative Layout Shift actually measures.
Interaction to Next Paint benefits in a different way. INP already reported the worst interaction, so the score was not inflated by length so much as it was unattributable. Knowing your worst interaction was 480 milliseconds is not actionable when you cannot say which screen it happened on. Now you can. We went deeper on the metric itself in our guide to Interaction to Next Paint.
Why is Time to First Byte reported as zero?
Because there is no byte. A soft navigation has no request to the document server, so there is no meaningful first byte to time, and Chrome reports TTFB as 0 rather than inventing a number.
We think this is the right call and we would rather have an obvious zero than a plausible fiction. But it does mean a dashboard that averages TTFB across all navigations will now show a number that falls as your app gets more single page app like, which is the opposite of an improvement signal. Exclude soft navigations from TTFB reporting rather than letting them dilute it.
This is the general shape of the upgrade. The new data is better and it will make some of your existing charts wrong in ways that look like wins.
Where does this show up in Chrome DevTools?
In the Performance panel, which can report soft navigations and their metrics, including a soft Largest Contentful Paint, alongside the ordinary timeline. For a developer chasing a slow route, that is the fastest loop available: record, navigate, read the per navigation numbers.
Field data is a separate question. What a browser reports locally and what appears in aggregated field datasets are two different pipelines, and we could not confirm this run how soft navigation metrics are treated in field reporting. Treat that as open rather than assuming it flows through, and check before you build a report on it.
The general point on measurement still applies, and we have written it before in our guide to Core Web Vitals and SEO: lab data tells you why, field data tells you whether. You need both, and you should not confuse one for the other.
What should a team actually do this quarter?
If you run a React, Vue or Next.js app with client side routing, start by checking whether your analytics reports anything at all after the first route. Most do not, and the honest answer for most teams is that they have never seen a number for the second page a user visits.
Then update the web-vitals library, feature detect on the soft-navigation entry type, and start collecting per route data before you change any code. A month of real numbers will tell you which route is the problem, and it will very often not be the one your team assumed.
One caution worth holding onto. This changes measurement, not experience. A route that was always slow is still slow; you can just see it now. The work that follows is the same work it has always been: less JavaScript on the critical path, fewer layout shifts from late loading content, and interactions that do not block the main thread.
If you want help reading the numbers, or a build that does not need this rescue in the first place, we are happy to walk through it. You can find us at phoenix.studio.
Want a site that performs like this?
Tell us about your project. We will come back with a clear next step, no pressure.
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.
Have a project like this?
Tell us where you want to go. We'll tell you how we'd get you there.