Currently, there is no standardized way to measure Core Web Vitals and other web performance metrics inside single page applications (SPA) because they rely on soft navigations to respond to user actions, which are not as obvious to detect as hard navigations.
As of now, web performance monitoring tools can’t easily report web performance metrics for soft navigations. Instead, most metrics focus on the initial page load.
The main reason for this measurement gap is that we don’t yet have an agreed definition of what user actions qualify as soft navigations, which would allow developers of web performance tools and browser vendors to coherently detect and measure Web Vitals in single page applications.
However, there’s already some progress in the development of soft navigation reporting.
The Chrome developer team have started work on defining the heuristics and creating the APIs for reporting web performance metrics for soft navigations. The development is currently at an experimental stage, and the proposals are still not set in stone.
We still decided to cover this topic because it will be an important change. Including soft navigations in Core Web Vitals will change the way we build, monitor, and optimize single page applications.
In this article, we’ll look into how navigation works in single page applications, the possibilities of soft navigation reporting, and the evolution of Google’s web-vitals.js library.
A Brief Recap of How SPAs Work
Single page applications provide an alternative web application architecture to websites and multi page applications (MPAs). They are typically created with component-based JavaScript frameworks such as React, Vue, Angular, and others.
As opposed to traditional websites and MPAs which download a new HTML page whenever the user performs an action (e.g. clicks a button or menu item), SPAs only download a single HTML page (this is where the name ‘Single Page Application’ comes from), then update the page content using JavaScript every time the user interacts with the app.
While the SPA architecture provides users with a more dynamic interface and can react to user actions faster than a traditional website, it’s hard to track in-app navigation in a single page application because, by default, the browser renders every piece of content (including partial and full page rewrites) under the same top-level URL.
As a result, early SPAs had various usability and SEO issues. For example, content changes were not followed by corresponding URL changes in the browser’s address bar, and search engine, social media, and other bots couldn’t index dynamic page content properly.
SPA developers solved these issues by introducing the concept of ‘soft navigations’, which maps ‘soft URLs’ to dynamically rendered pages. They are called ‘soft’ because the browser doesn’t download a new HTML file in the background when the app generates a dynamic URL for the updated page content.
Despite the addition of soft navigations to single page applications, website analysis and monitoring tools still don't have an established way to measure in-app performance and other metrics. Let’s see why.
What Is a Soft Navigation?
A soft navigation is essentially the dynamic emulation of the corresponding hard navigation that would be used in a website or multi page application.
To expose dynamic content changes to the web browser and other user agents, SPAs update the URLs dynamically and push the previous URLs into the browser’s history using the History API.
As a result, on the surface, the updated page content will behave like a new HTML page — e.g. users will be able to see a new URL in the browser’s address bar, bookmark the page, share it on social media, use the browser’s Back and Forward buttons, etc.
Hard Navigation vs Soft Navigation: A Code Example
Now let’s see a code example of the technical differences between soft and hard navigations.
An Example of Hard Navigations
In the code snippet below, the three links in the <header>
section are hard navigations and could be used on a static HTML page:
<!-- Hard navigation with HTML -->
<!-- index.html -->
<header>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</header>
To allow the user to navigate this website, we would also need to create an about.html
and a contact.html
file and upload them to the server.
The Corresponding Soft Navigations (in React)
Now, let’s see how the corresponding soft navigations look like in a single page application created with the React framework:
/**
* Soft navigations with React
* components/Header.js
*/
import { Link } from "react-router-dom";
const Header = () => {
return (
<header>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Link to="/contact">Contact</Link>
</header>
);
};
export default Header;
And, here's the respective App.js
file:
/**
* Soft navigations with React
* App.js
*/
import {
BrowserRouter as BrowserRouter,
Route,
Routes,
} from "react-router-dom";
import Header from "./components/Header";
import Home from "./components/Home";
import About from "./components/About";
import Contact from "./components/Contact";
function App() {
return (
<BrowserRouter>
<div className="container">
<Header />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</div>
</BrowserRouter>
);
}
export default App;
To allow the user to navigate the React app above, we would also need to create the Home.js
, About.js
, and Contact.js
components and add them to the application.
All in all, the browser will compile the React code above to the same HTML as in the first code snippet above.
Under the hood, the Link
element uses history.pushState
to update the page URL without fully reloading the page.
However, there’s an important difference:
Now, the three links in the <header>
section are soft navigations.
When the user clicks one of them, the browser will update the page content and URL on the fly using JavaScript instead of sending an HTTP request to the server and downloading a new HTML page.
The React app will respond faster than the traditional HTML website because the entire code is already downloaded, the JavaScript is compiled, and no more client-server communication is needed.
However, capturing web performance metrics for a dynamic update of a single HTML page (i.e. soft navigation) is not as straightforward as measuring the performance impact of a static URL change where a real HTML page load takes place (i.e. hard navigation).
The Challenges of Reporting Soft Navigations
The issue with soft navigation reporting is that each SPA framework (or application in the case of native ECMAScript components) defines soft navigations in a different way.
If there’s just a single HTML page, which page updates qualify as ‘navigation’? There’s no standardized answer to this question.
For example, different SPAs can:
- update the URL on different types of content changes
- load content synchronously or asynchronously
- update the URL before or after the new content loads
- preload the content or not
- … and more
However, if each SPA follows different rules to update the URL, how should website analytics tools consistently detect soft navigations and report their performance impact?
To measure Core Web Vitals and other performance metrics for soft navigations, we need a standardized way that can be used for any single page application, irrespective of the technology it was built with.
The Standardization and Implementations of Soft Navigations
The defining rules to identify soft navigations (a.k.a. heuristics) and the corresponding technical implementations are still in the drafting stage.
According to the current version of the specifications (which can still change in the future), the following dynamic URL updates qualify as soft navigations:
- “The navigation is initiated by a user action.”
- “The navigation results in a visible URL change to the user, and a history change.”
- “The navigation results in a DOM change.”
(Source: Chrome for Developers Blog)
The heuristics above are being/will be implemented at the following levels:
W3C Specifications and Web APIs
There’s already an unofficial Soft Navigations draft published by W3C’s Web Platform Incubator Community Group (WICG), however it’s still in an early stage.
Once soft navigation reporting is added to the native browser APIs, they will be part of the Performance
interface.
The addition of soft navigation detection to actual web APIs depends on the three browser engine developers: Chromium (Blink engine), Firefox (Gecko engine), and Safari (WebKit engine) — as of the currently available information, only Chromium is working on the feature.
Browser Developer Tools
Soft navigation detection is already available as an experimental feature of Chrome DevTools.
You can report the occurrence of soft navigations to the console by enabling the ‘Enable Experimental Web Platform Features’ flag in your Chrome browser:
The Web Vitals Initiative and Its Experimental soft-navs
Branch
Currently, the performance impact of soft navigations is not included in the official Web Vitals metrics, so they are not available in either Chrome DevTools or Google’s lab tools (e.g. Lighthouse).
However, Google Chrome’s web-vitals.js library has an experimental soft-nav branch that already includes working code that you can use to report Web Vitals for soft navigations.
For example, here’s the simplest way to add soft navigation reporting for Largest Contentful Paint, Interaction to Next Paint, and Cumulative Layout Shift to your application:
import {
onLCP,
onINP,
onCLS,
} from "https://unpkg.com/web-vitals@soft-navs/dist/web-vitals.js?module";
onLCP(console.log, { reportSoftNavs: true });
onINP(console.log, { reportSoftNavs: true });
onCLS(console.log, { reportSoftNavs: true });
The CrUX Report
Similar to Lighthouse, soft navigation reporting is not included in the Chrome User Experience report either, which also only reports Core Web Vitals for hard navigations. Google still has no known plans to add the impact of soft navigations to the CrUX report.
The Current State of Soft Navigation Reporting
As of Chrome for Developers’ article on soft navigation reporting, the experimental branch of the web-vitals.js library currently reports soft navigations for the individual Web Vitals as follows (however, note that the data below is still subject to change):
- Time To First Byte (TTFB) for soft navigations is always reported as 0 because TTFB is a server-side metric. When a soft navigation happens, there's no communication with the server, and the ‘first byte’ is already present in the browser (i.e. the page is already downloaded).
- First Contentful Paint (FCP) is currently not supported by the experimental branch of the web-vitals.js library, but there are plans to register the timestamp when the dynamic URL changes and to use it as the FCP value for the soft navigation.
- Largest Contentful Paint (LCP) doesn’t include the existing paints from the previous navigation, whether it was a hard or soft navigation. Its value is either 0 or a positive number. The latter happens when the largest content element on the updated page has appeared during that update.
- Cumulative Layout Shift (CLS) reports the amount of unexpected layout shifts that have happened since the last navigation.
- First Input Delay (FID) is currently not supported by the experimental branch of the web-vitals.js library, which as of now only registers the first first input delay (which follows the initial hard navigation), but there are plans to register the first input delays for the subsequent soft navigations too.
- Interaction to Next Paint (INP) reports the 98th percentile value of all interaction delays that have happened since the previous (soft or hard) navigation.
In other words, Web Vitals (except FCP and FID, which are currently not supported by the library) are split into smaller parts so that you can see the performance impacts of the subsequent soft navigations and separate them from the initial hard navigation, which has resulted in the download of the entire code base of the single page application.
Wrapping Up
In this article, we looked into the current state, the existing implementations, and the future possibilities of reporting Web Vitals for soft navigations.
The comparison table below highlights the main differences between soft navigations vs. hard navigations:
Soft navigation | Hard navigation | |
---|---|---|
Definition | a page update executed by JavaScript | a new HTML page download from the server |
URL change | dynamic (the URL is updated by JavaScript, e.g. using the React Router package, the browser only ‘sees’ the top-level domain) | static (the URL is changed by HTTP, which is the result of real browser-server communication) |
Use cases | single page applications created with component-based UI frameworks (e.g. React, Angular, Vue, etc.) or ES6 modules | websites, multi page applications |
Speed | reacts faster to user actions | response to user actions takes at least one round-trip to the server |
Monitoring | hard to track and monitor (no standardized definition and technical implementation of how to detect and measure dynamic URL changes) | easy to track and monitor |
As I mentioned above, the standardization of soft navigation reporting is still in the early stages. Here are some resources where you can find more information about the current state of the process and contribute if you want to:
- The issue tracker of WICG’s Soft Navigations repo on GitHub
- Soft Navigations Change Lists (CLs) on Chromium Gerrit
- Chrome Origin Trial for Soft Navigation Heuristics — you can register if you want to participate in the experiments
- The Experimenting with measuring soft navigations article on the ‘Chrome for Developers’ blog by Barry Pollard and Yoav Weiss