<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Paweł Pokrywka</title>
    <description>The latest articles on Forem by Paweł Pokrywka (@paweldev).</description>
    <link>https://forem.com/paweldev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3479958%2F60112047-52dd-4b4d-86ce-e4c583e47fa8.jpg</url>
      <title>Forem: Paweł Pokrywka</title>
      <link>https://forem.com/paweldev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/paweldev"/>
    <language>en</language>
    <item>
      <title>How fast do websites load from Google Search? Comparing various prefetching and on-demand load methods.</title>
      <dc:creator>Paweł Pokrywka</dc:creator>
      <pubDate>Sat, 13 Sep 2025 17:20:21 +0000</pubDate>
      <link>https://forem.com/paweldev/how-fast-do-websites-load-from-google-search-comparing-various-prefetching-and-on-demand-load-4i1c</link>
      <guid>https://forem.com/paweldev/how-fast-do-websites-load-from-google-search-comparing-various-prefetching-and-on-demand-load-4i1c</guid>
      <description>&lt;p&gt;In the previous part, we saw that a &lt;a href="https://www.pawelpokrywka.com/p/different-methods-of-prefetching" rel="noopener noreferrer"&gt;website can load in 15 different ways&lt;/a&gt; when visited from the Google results page and how to measure the performance impact of each load type.&lt;/p&gt;

&lt;p&gt;In this post, I will share the results of my website's performance measurement, along with my comments.&lt;/p&gt;

&lt;p&gt;I assume you are familiar with the differences between various page load types. If you need a refresher, please read the &lt;a href="https://www.pawelpokrywka.com/p/different-methods-of-prefetching" rel="noopener noreferrer"&gt;previous post&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This post is part of a series on Signed Exchanges (SXG). To assess SXG’s impact, I measured the performance of different page load types. This article is based on my research and summarizes my findings.&lt;/p&gt;

&lt;p&gt;SXG is a technology to make your website load faster for Google-referred users. If you want to implement it on your website, start &lt;a href="https://www.pawelpokrywka.com/p/how-i-took-lcp-down-under-350ms" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;For the impatient, here are the results showing the Largest Contentful Paint (LCP) 75th percentiles I measured (or estimated as stated in the labels and explained later in the text).&lt;/p&gt;

&lt;p&gt;I show that it’s possible to go below half a second, but SXG side effects may worsen the experience for some users. HTML-only prefetching improved the performance, but sometimes only slightly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftth9ltomc3c53jjjhnm3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftth9ltomc3c53jjjhnm3.png" width="800" height="266"&gt;&lt;/a&gt;&lt;/p&gt;
P75 LCP comparison between page load types. The less, the better. Some values were estimated as stated in the labels.



&lt;h2&gt;
  
  
  Your mileage may vary
&lt;/h2&gt;

&lt;p&gt;These results are based on real user data from my specific website, measured from Polish users. Your results may vary significantly based on your website's architecture, user geography, CDN configuration, and other factors. The patterns I observed, particularly the desktop TTFB issues, may be unique to my setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Methodology
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Measured pages
&lt;/h3&gt;

&lt;p&gt;I share my findings focusing on a specific section of my website: the vendor index page (similar to a product index page in e-commerce). While I measured other sections as well, including them here would add length and complexity without providing significant additional value.&lt;/p&gt;

&lt;p&gt;The vendor index page receives significant traffic from Google and has strong performance metrics, making it an ideal candidate for comparison testing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Chosen page load types
&lt;/h3&gt;

&lt;p&gt;The results include all the page load types I identified in the &lt;a href="https://www.pawelpokrywka.com/p/different-methods-of-prefetching" rel="noopener noreferrer"&gt;previous post&lt;/a&gt;, except for types related to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Accelerated Mobile Pages (AMP)&lt;/strong&gt;, as my website doesn’t use it&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Google Ads&lt;/strong&gt; (no ad campaigns for the measured section of the website at the moment of data collection)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Early Hints&lt;/strong&gt; , because my website uses HTML edge caching (more on this later)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Data collection conditions
&lt;/h3&gt;

&lt;p&gt;I collected data only from visits that met the following conditions. For the explanation of why these specific conditions were necessary, see my &lt;a href="https://www.pawelpokrywka.com/p/different-methods-of-prefetching" rel="noopener noreferrer"&gt;previous post&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;It’s a Google-referred visit&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The user visited the website for the first time (checked with a local storage marker item)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The page has been opened in an existing tab&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The browser supported SXG&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of the above data was sent to DebugBear, a monitoring platform.&lt;/p&gt;

&lt;h3&gt;
  
  
  Filters applied to collected data
&lt;/h3&gt;

&lt;p&gt;Then I used its filtering functionality to include only:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Users visiting the website from Poland&lt;/strong&gt; , as Polish users are my target population, so I wanted to understand how they experience website performance.&lt;br&gt;&lt;br&gt;
Additionally, filtering by location helps exclude bots that typically visit my website from other countries.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Visits not using cached assets&lt;/strong&gt; , as the local storage check (described above) did not always work properly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Visits with Time To First Byte (TTFB) equaling zero for load types not involving prefetching&lt;/strong&gt;. It’s practically impossible to receive the first byte of the response below 1 millisecond; therefore, I treated those samples as invalid.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Page load types that can be reliably measured&lt;/strong&gt; by excluding SXG redirects. I'll explain this exclusion later.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After the above filtering, I was left with over 14k data points.&lt;/p&gt;

&lt;h3&gt;
  
  
  Obtained performance metrics
&lt;/h3&gt;

&lt;p&gt;I segmented the data by page load type and device category to generate LCP histograms and calculate 75th percentiles and averages for each combination.&lt;/p&gt;

&lt;p&gt;For calculating averages, I removed outliers by excluding visits with LCP over 5 seconds.&lt;/p&gt;

&lt;p&gt;I rely on 75th percentiles by default. When using averages, I always explicitly mention it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Estimations
&lt;/h3&gt;

&lt;p&gt;I estimated LCP for page load types that use SXG redirects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;When estimating averages, I added a bias correction—which I determined was necessary for accuracy—to the reference average.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For estimating 75th percentiles, I observed a correlation between percentiles and averages. I then leveraged this relationship to calculate missing percentiles from the available averages.&lt;br&gt;&lt;br&gt;
Note that the estimated percentiles were derived from estimated averages, creating a dependency chain in the calculations.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’ll provide more details later in the text.&lt;/p&gt;

&lt;h3&gt;
  
  
  Spreadsheet with data
&lt;/h3&gt;

&lt;p&gt;The spreadsheet below contains the LCP data I collected from DebugBear, along with comparison charts. It’s composed of 2 worksheets for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;75th percentiles&lt;/li&gt;
&lt;li&gt;averages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each worksheet contains a configuration section where you can experiment with the estimation parameters to see how the results change.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.pawelpokrywka.com/api/v1/file/211ea60a-1de5-4992-b114-b301c3afe2bc.xlsx" rel="noopener noreferrer"&gt;Download LCP analysis of page load types&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Disclaimer
&lt;/h3&gt;

&lt;p&gt;I’m not a data scientist, but I did my best to ensure the data is reliable and the conclusions sound.&lt;/p&gt;

&lt;h2&gt;
  
  
  LCP results
&lt;/h2&gt;

&lt;p&gt;Below, you can see the comparison of the speed (LCP) of page load types. It’s the same chart as the one at the beginning of this post. I put it here, so you can reference it easily while reading my observations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftth9ltomc3c53jjjhnm3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftth9ltomc3c53jjjhnm3.png" width="800" height="266"&gt;&lt;/a&gt;&lt;/p&gt;
P75 LCP comparison between page load types. The less, the better. Some values were estimated as stated in the labels.



&lt;h3&gt;
  
  
  Desktop vs mobile
&lt;/h3&gt;

&lt;p&gt;Looking at the chart, you can see that half of the page load types work better on desktop, while the other half works better on mobile:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;When the page was loaded from Google (SXG On-Demand Load) or prefetched, the desktop won.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When the browser was talking to Cloudflare (Server Load, Edge Cache Load, and both SXG Redirects), mobile came out on top.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I thought desktop users should have a better experience than mobile users because of better connection quality and more CPU power. Why then it’s not universally applicable to all page load types?&lt;/p&gt;

&lt;h3&gt;
  
  
  TTFB component of LCP
&lt;/h3&gt;

&lt;p&gt;When I dug deeper into the data, I found that for Cloudflare loads, on desktop, TTFB contributed to 36-51% of LCP, while on mobile, it accounted for 22-33%. Compare the TTFB histograms below—mobile looks much better:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkwbh4rv6qyluy2y4ol04.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkwbh4rv6qyluy2y4ol04.png" width="800" height="183"&gt;&lt;/a&gt;&lt;/p&gt;
The TTFB histogram for the Edge Cache Load on mobile. The green, dashed line marks the 75th percentile.



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkrq812eqhau7ered7k8p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkrq812eqhau7ered7k8p.png" width="800" height="183"&gt;&lt;/a&gt;&lt;/p&gt;
The TTFB histogram for the Edge Cache Load on desktop. The green, dashed line marks the 75th percentile.



&lt;p&gt;For prefetched pages, TTFB is zero or near zero, so it’s useless for comparisons. But when the pages were loaded from Google SXG cache on demand, the TTFB was better on desktop (the opposite of the previous case). On desktop, TTFB contributed to 25% of LCP, while on mobile, it accounted for 30%.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcv30nmqhfh8665kw3ysx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcv30nmqhfh8665kw3ysx.png" width="800" height="183"&gt;&lt;/a&gt;&lt;/p&gt;
The TTFB histogram for the SXG On-Demand Load on mobile. The green, dashed line marks the 75th percentile.



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj5k8akfcv2wh2gxniqtr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj5k8akfcv2wh2gxniqtr.png" width="800" height="183"&gt;&lt;/a&gt;&lt;/p&gt;
The TTFB histogram for the SXG On-Demand Load on desktop. The green, dashed line marks the 75th percentile.



&lt;p&gt;&lt;strong&gt;Why did waiting for the first byte take longer on a desktop than on a mobile for Cloudflare loads?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I hypothesize that in Poland, Cloudflare edge servers have better network connectivity with mobile ISPs than with residential ones. This could be because there are only a few major mobile operators, while many residential ISPs exist.&lt;/p&gt;

&lt;p&gt;From Cloudflare's perspective as a global infrastructure provider, Poland might be considered a relatively small market, leading them to potentially prioritize peering agreements with the larger operators.&lt;/p&gt;

&lt;p&gt;Note that this is specific to my measurements in Poland and may not apply elsewhere.&lt;/p&gt;

&lt;p&gt;If you have a better explanation, drop a comment below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reference page load type for comparisons
&lt;/h3&gt;

&lt;p&gt;In this post, I compare the P75 LCP of each page load type to that of a baseline on-demand page load from the server:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mobile:&lt;/strong&gt; 1.43 seconds&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Desktop:&lt;/strong&gt; 1.82 seconds&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;The gap between mobile and desktop is 390 ms.&lt;/p&gt;

&lt;p&gt;Keep in mind that the desktop LCP in this study is heavily impacted by the increased TTFB, as discussed above. This impact may not be present in different countries and/or in the future. This makes it difficult to draw general conclusions (i.e., unrelated to my specific website) based on comparisons of page load types on desktop and between device categories.&lt;/p&gt;

&lt;p&gt;On the other hand, the mobile performance characteristics should be fairly universal.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Below, you can see the LCP histograms with the TTFB impact on desktop clearly visible:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr2v9kngonp0cmx13oukz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr2v9kngonp0cmx13oukz.png" width="800" height="183"&gt;&lt;/a&gt;&lt;/p&gt;
The LCP histogram for the Server Load on mobile. The green, dashed line marks the 75th percentile.



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5e5bwqp86skfo1t5mami.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5e5bwqp86skfo1t5mami.png" width="800" height="183"&gt;&lt;/a&gt;&lt;/p&gt;
The LCP histogram for the Server Load on desktop. The green, dashed line marks the 75th percentile.



&lt;h3&gt;
  
  
  SXG prefetching with subresources is unbeatable
&lt;/h3&gt;

&lt;p&gt;You probably won’t be surprised that prefetching the entire website (document and subresources) using SXG is a definitive winner in terms of performance. Compared to the reference, on-demand load from the server:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mobile:&lt;/strong&gt; -846 ms&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Desktop:&lt;/strong&gt; -1356 ms&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;As you observed above, I mark LCP improvements with a minus sign (LCP decrease, which is what we want). I use plus sign for LCP degradations (LCP increases, which we should avoid). I don’t use signs before absolute values, as you can see below.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As a result, the LCP is as low as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mobile:&lt;/strong&gt; 584 ms&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Desktop:&lt;/strong&gt; 464 ms&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;A half-second LCP feels almost unreal? If you’d like more insights like this, &lt;a href="https://www.pawelpokrywka.com/welcome" rel="noopener noreferrer"&gt;subscribe to my newsletter&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Below you will find LCP histograms. Please note that the data points with values higher than 1.5 seconds are almost non-existent!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr9awr6pr0xafo7s87b21.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr9awr6pr0xafo7s87b21.png" width="800" height="183"&gt;&lt;/a&gt;&lt;/p&gt;
The LCP histogram for the SXG Prefetch with Subresources on mobile. The green, dashed line marks the 75th percentile.



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F47489ewhmv04o3kinfx9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F47489ewhmv04o3kinfx9.png" width="800" height="183"&gt;&lt;/a&gt;&lt;/p&gt;
The LCP histogram for the SXG Prefetch with Subresources on desktop. The green, dashed line marks the 75th percentile.



&lt;h3&gt;
  
  
  Can this be improved even further?
&lt;/h3&gt;

&lt;p&gt;My data shows that for SXG-prefetched pages with subresources, 95% of LCP time is spent on rendering.&lt;/p&gt;

&lt;p&gt;For extremely low LCP: use large images or videos freely (they're prefetched anyway) &lt;em&gt;and&lt;/em&gt; keep your page structure lean and simple (to minimize rendering). The first won't hurt; the second fixes the real bottleneck.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Second place belongs to HTML-only prefetching&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;While this may seem obvious, it's worth stating clearly: prefetching improves performance regardless of whether you use Speculation Rules or SXG, even when only the HTML document is prefetched. Compared to the reference on-demand load from the server, HTML-only prefetching is faster by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mobile:&lt;/strong&gt; from -20 to -100 ms&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Desktop:&lt;/strong&gt; from -670 to -740 ms&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The mobile results appear subpar, but they align roughly with the &lt;a href="https://developer.chrome.com/blog/search-speculation-rules#impact-first-two" rel="noopener noreferrer"&gt;results reported by Google itself&lt;/a&gt;. The desktop performance improvement is significant, mostly because prefetching is a workaround for Cloudflare’s TTFB issue.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I believe inlining critical subresources, such as important CSS fragments, should make HTML-only prefetching more performant. However, I haven’t implemented it on my website (yet).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Speculation Rules are slightly better than SXG for prefetching HTML
&lt;/h3&gt;

&lt;p&gt;A page prefetched using Speculation Rules loads a bit faster than the one prefetched using SXG &lt;strong&gt;without&lt;/strong&gt; subresources. That’s interesting, since both methods technically do the same thing.&lt;/p&gt;

&lt;p&gt;On the histograms below, you can see that SXG has more extreme samples (below 250 milliseconds and 5+ seconds), while Speculation Rules’ samples are much more concentrated below 1 second.&lt;/p&gt;

&lt;p&gt;I suspect the difference may be explained in part by the cryptography-related CPU overhead of SXG.&lt;/p&gt;

&lt;h4&gt;
  
  
  Mobile histograms
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1i1nypf74s3gvn65br0n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1i1nypf74s3gvn65br0n.png" width="800" height="183"&gt;&lt;/a&gt;&lt;/p&gt;
The LCP histogram for the Speculation Rules Prefetch on mobile. The green, dashed line marks the 75th percentile.



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fffltrcsyzpu4v9o34vi5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fffltrcsyzpu4v9o34vi5.png" width="800" height="183"&gt;&lt;/a&gt;&lt;/p&gt;
The LCP histogram for the SXG Prefetch without Subresources on mobile. The green, dashed line marks the 75th percentile.



&lt;h4&gt;
  
  
  Desktop histograms
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2b53g081eubqbqmrdsz2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2b53g081eubqbqmrdsz2.png" width="800" height="183"&gt;&lt;/a&gt;&lt;/p&gt;
The LCP histogram for the Speculation Rules Prefetch on desktop. The green, dashed line marks the 75th percentile.



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmh4bplrmek4hhirkyov7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmh4bplrmek4hhirkyov7.png" width="800" height="183"&gt;&lt;/a&gt;&lt;/p&gt;
The LCP histogram for the SXG Prefetch without Subresources on desktop. The green, dashed line marks the 75th percentile.



&lt;h3&gt;
  
  
  Edge caching was worth it
&lt;/h3&gt;

&lt;p&gt;Introducing HTML edge caching lets me skip processing frequently accessed pages on my server. Also, as Cloudflare works as a reverse proxy, these pages don’t need to be transferred between my server and Cloudflare on each request. They are kept on an optimized infrastructure and retrieved in milliseconds.&lt;/p&gt;

&lt;p&gt;This is visible in LCP measurements, when comparing on-demand page loads from the server and the edge cache:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mobile:&lt;/strong&gt; -120 ms&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Desktop:&lt;/strong&gt; -350 ms&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Given that edge caching improves all the traffic, not only Google-referred, I think the improvement is satisfactory, especially on desktop.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F82zwp7b04cj8xr1m1zjv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F82zwp7b04cj8xr1m1zjv.png" width="800" height="183"&gt;&lt;/a&gt;&lt;/p&gt;
The LCP histogram for the Edge Cache Load on mobile. The green, dashed line marks the 75th percentile.



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp5sbjpuzz52b350j5a30.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp5sbjpuzz52b350j5a30.png" width="800" height="183"&gt;&lt;/a&gt;&lt;/p&gt;
The LCP histogram for the Edge Cache Load on desktop. The green, dashed line marks the 75th percentile.



&lt;p&gt;On the histogram above, you can see the absolute LCP values:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mobile:&lt;/strong&gt; 1.31 seconds&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Desktop:&lt;/strong&gt; 1.47 seconds&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;This time, the gap between mobile and desktop is 160 ms—a 2.5x decrease compared to the reference &lt;em&gt;Server Load&lt;/em&gt;. I found it’s caused by TTFB again, but why there is less difference now?&lt;/p&gt;

&lt;p&gt;I don’t know. If you have an explanation, drop a comment below.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Proper HTML edge caching makes Early Hints' impact negligible
&lt;/h3&gt;

&lt;p&gt;As I mentioned at the beginning, I didn't include measurements for the Early Hints page load type.&lt;/p&gt;

&lt;p&gt;Cloudflare caches the &lt;strong&gt;Link&lt;/strong&gt; HTTP header from the &lt;strong&gt;server’s first response&lt;/strong&gt; and then reuses it to send Early Hints in &lt;strong&gt;later responses&lt;/strong&gt; for the same URL.&lt;/p&gt;

&lt;p&gt;However, when the edge cache is in use, then not only the Link header, but the entire HTTP response is cached. In effect, those later responses almost always include the full page directly from the cache. In that case, the origin server is never contacted, so there’s no gap between sending Early Hints and delivering the complete response. As a result, Early Hints provide no performance benefit.&lt;/p&gt;

&lt;p&gt;Because of this, I wasn’t able to collect enough samples of requests that actually went to the origin server, where Early Hints could make a difference.&lt;/p&gt;

&lt;h3&gt;
  
  
  SXG On-Demand Load impact varies between device categories
&lt;/h3&gt;

&lt;p&gt;When the SXG version of a page isn't prefetched, it must be fetched on demand. The reasons for this were explained in the &lt;a href="https://www.pawelpokrywka.com/p/different-methods-of-prefetching" rel="noopener noreferrer"&gt;previous part&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this scenario, compared to the reference, the LCP difference was as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Mobile: +240 ms&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Desktop: -210 ms&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Mobile
&lt;/h4&gt;

&lt;p&gt;The slowdown on mobile can be attributed to the network overhead of downloading SXG subresources on demand. Each subresource may require its own certificate file to be fetched, which in the worst case can double the number of files that need to be downloaded. The higher latency of mobile connections makes this effect more visible.&lt;/p&gt;

&lt;p&gt;Also, SXG processing requires performing cryptographic operations. Mobile CPUs often have less processing power, which can make the overhead more visible and further contribute to LCP degradation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F29ax6as9rp69eiechrn0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F29ax6as9rp69eiechrn0.png" width="800" height="183"&gt;&lt;/a&gt;&lt;/p&gt;
The LCP histogram for the SXG On-Demand Load on mobile. The green, dashed line marks the 75th percentile.



&lt;h4&gt;
  
  
  Desktop
&lt;/h4&gt;

&lt;p&gt;I attribute the desktop LCP improvement to the TTFB issue, as explained earlier. Normally, I would expect a slight performance degradation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffqj78x0kbl56m2u8h5e6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffqj78x0kbl56m2u8h5e6.png" width="800" height="183"&gt;&lt;/a&gt;&lt;/p&gt;
The LCP histogram for the SXG On-Demand Load on desktop. The green, dashed line marks the 75th percentile.



&lt;h2&gt;
  
  
  The dark side of SXG
&lt;/h2&gt;

&lt;p&gt;As you have seen above, loading the page on demand from the Google SXG cache is not optimal, at least on mobile devices. That’s not good for the technology that promised to improve page load speed. But the real problem is much worse.&lt;/p&gt;

&lt;h3&gt;
  
  
  SXG fallback client-side redirect
&lt;/h3&gt;

&lt;p&gt;It occurs when the page is missing from Google’s SXG cache. When the user decides to navigate to the target page, the SXG cache serves a fallback page to the browser. This page contains a client-side JavaScript code that redirects the browser to the target page.&lt;/p&gt;

&lt;p&gt;It would be interesting to know how much this degrades performance!&lt;/p&gt;

&lt;h3&gt;
  
  
  RUM tools blind you to the SXG performance problems users experience
&lt;/h3&gt;

&lt;p&gt;I collected many samples with detailed performance measurements for page loads redirected from the Google SXG cache. Unfortunately, I decided to throw them out entirely.&lt;/p&gt;

&lt;p&gt;I noticed they don’t make sense at all: they seemed to have no impact on LCP. No improvement (that’s obvious), but also no expected degradation.&lt;/p&gt;

&lt;p&gt;Why? The answer is straightforward; however, it took me a while to understand. The bottom line is that it's &lt;strong&gt;impossible&lt;/strong&gt; to measure this in production. No matter which Real User Monitoring (RUM) solution you use, you won’t be able to say if you have a performance issue!&lt;/p&gt;

&lt;h3&gt;
  
  
  Start of navigation bias
&lt;/h3&gt;

&lt;p&gt;When a user is on page A and clicks a standard link to page B, LCP measurement begins at the moment of the click. This way, the LCP value reflects the entire wait time for the largest element on the target page, including network delays, HTTP redirects, and other overhead.&lt;/p&gt;

&lt;p&gt;The situation changes when an SXG-fallback intermediary page uses JavaScript to redirect the user to the target page. In this case, the browser cannot distinguish whether the redirect was triggered by the user or automatically. It assumes a user action and starts measuring time only from the moment the client-side redirect occurs—not from the original click on the Google search results page.&lt;/p&gt;

&lt;p&gt;This means the interval between the click and the client-side redirect—I'll call this &lt;em&gt;click-to-redirect&lt;/em&gt;—is invisible to the browser. And that gap can be significant. In my tests, it ranged anywhere from 50 to 2000 ms, depending on the device, browser, connection type, and likely other factors.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I built a &lt;a href="https://www.planujemywesele.pl/sxg-tests/fallback" rel="noopener noreferrer"&gt;SXG fallback redirect demo&lt;/a&gt; so you can try this yourself and see the difference. The demo also lets you simulate SXG and Speculation Rules prefetching, but in my testing, those didn’t affect the results.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Estimating the average SXG fallback LCP
&lt;/h3&gt;

&lt;p&gt;The LCP of a page loaded using SXG fallback redirect should be a sum of the LCP of the underlying page load type (&lt;em&gt;Server Load&lt;/em&gt; or &lt;em&gt;Edge Cache Load&lt;/em&gt;) and the &lt;em&gt;click-to-redirect&lt;/em&gt; time.&lt;/p&gt;

&lt;p&gt;The HTML of the SXG fallback redirect page is under 350 bytes, so the time it takes for the page to load and start the redirect is almost the same as its TTFB.&lt;/p&gt;

&lt;p&gt;If the TTFB of the fallback page is similar to the TTFB of &lt;em&gt;SXG On-Demand Load&lt;/em&gt; (which it should be, since both responses are generated by the same system), I could use the already collected data.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I prepared a demo that measures TTFB of a SXG On-Demand Load. In my tests, I could confirm that the TTFB roughly equals &lt;em&gt;click-to-redirect&lt;/em&gt; time. You can check it by yourself &lt;a href="https://www.planujemywesele.pl/sxg-tests/ttfb" rel="noopener noreferrer"&gt;here&lt;/a&gt; and compare the result with the measurement from the &lt;a href="https://www.planujemywesele.pl/sxg-tests/fallback" rel="noopener noreferrer"&gt;previous demo&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For the initial estimation, I will use averages because they are easier to work with than percentiles.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fslm733fou0ozl0duk1k3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fslm733fou0ozl0duk1k3.png" width="800" height="183"&gt;&lt;/a&gt;&lt;/p&gt;
The TTFB histogram for the SXG On-Demand Load on mobile. The green, dashed line marks the average. TTFB measurements corresponding to LCP measurements exceeding 5 seconds are not included, as explained in the methodology section.



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2wvfeyd2wk33jvu2bw41.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2wvfeyd2wk33jvu2bw41.png" width="800" height="183"&gt;&lt;/a&gt;&lt;/p&gt;
The TTFB histogram for the SXG On-Demand Load on desktop. The green, dashed line marks the average. TTFB measurements corresponding to LCP measurements exceeding 5 seconds are not included, as explained in the methodology section.



&lt;p&gt;The average &lt;em&gt;click-to-redirect&lt;/em&gt; delay that should be added to the underlying, average page load type LCP is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mobile&lt;/strong&gt; : +403 ms&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Desktop&lt;/strong&gt; : +311 ms&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find the estimated absolute average LCP values ​​in the spreadsheet mentioned earlier. Below is a chart comparing the average LCP for each page load type. The conclusions are roughly similar to those for the 75th percentile.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9gy0ogtg2thrynkus26c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9gy0ogtg2thrynkus26c.png" width="800" height="294"&gt;&lt;/a&gt;Average LCP comparison between page load types. The less, the better. Some values were estimated as stated in the labels. &lt;/p&gt;

&lt;h3&gt;
  
  
  Estimating the 75th percentile LCP for SXG fallback
&lt;/h3&gt;

&lt;p&gt;I see the 75th percentile as the standard way of assessing LCP performance. It's used by the Chrome User Experience Report (&lt;a href="https://developer.chrome.com/docs/crux" rel="noopener noreferrer"&gt;CrUX&lt;/a&gt;). Therefore, we should estimate it for SXG fallback page load types.&lt;/p&gt;

&lt;p&gt;In the case of my data, I found that for each page load type, its 75th percentile can be calculated by increasing the average by 20-40%. Therefore, I assumed that the missing 75th percentile for SXG Redirect loads can be estimated using this method. For the exact calculations, see the spreadsheet included earlier.&lt;/p&gt;

&lt;h3&gt;
  
  
  SXG fallback significantly hurts performance
&lt;/h3&gt;

&lt;p&gt;Below, you can see how the P75 LCP degrades when the page loads using SXG fallback compared to the reference:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;For Server Load&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For Edge Cache Load&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The LCP increase for the Edge Cache Load on desktop doesn’t seem much, but keep in mind that it has erased all the edge caching gains.&lt;/p&gt;

&lt;p&gt;In my opinion, the performance degradation for pages loaded using SXG fallback is substantial. &lt;strong&gt;And neither Google nor Cloudflare documentation will tell you this.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;While these specific performance numbers are from my website, the measurement blind spot I've identified is a fundamental issue that affects all SXG implementations.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Enjoy uncovering hidden insights?&lt;/strong&gt; I share experiments, lessons learned, and surprising discoveries from the problems I dig into. If you like finding out what others overlook, you’ll enjoy &lt;a href="https://www.pawelpokrywka.com/welcome" rel="noopener noreferrer"&gt;my newsletter&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  SEO is not impacted
&lt;/h3&gt;

&lt;p&gt;There’s an interesting side effect of the fact that LCP for SXG fallbacks cannot be measured accurately in production.&lt;/p&gt;

&lt;p&gt;Chrome browsers continuously report LCP (along with the other Core Web Vitals) to Google, which then publishes the aggregated data as CrUX.&lt;/p&gt;

&lt;p&gt;In my experiment, Chrome reported &lt;strong&gt;incomplete LCP data&lt;/strong&gt; to Google when a page was loaded via an SXG fallback redirect. I throttled the network to 3G and simulated the fallback. The LCP shown in Chrome DevTools’ Performance panel (screenshot below, left) matched what Chrome reported to Google (screenshot below, right). Neither measurement included the &lt;em&gt;click-to-redirect&lt;/em&gt; delay.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc3sb8t4tap5fz6bntchr.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc3sb8t4tap5fz6bntchr.jpg" width="800" height="347"&gt;&lt;/a&gt;&lt;/p&gt;
Left: LCP measured in Chrome DevTools’ Performance panel. Right: Chrome’s URL-Keyed Metrics (UKM) page showing the values that will be uploaded to Google.



&lt;p&gt;As you know, LCP is a ranking factor: a good score (≤ 2.5 seconds) should boost your SERP positions. And how does Google get that data? From CrUX.&lt;/p&gt;

&lt;p&gt;Now imagine this scenario: your LCP sits right at 2.5 seconds. You enable SXG, but don’t configure it properly. As a result, most of your pages load through a fallback redirect. Your &lt;em&gt;real&lt;/em&gt; LCP rises above 2.5 seconds, degrading UX. But Google still sees the optimistic value from CrUX and continues to treat your site as if it had a good LCP—effectively ranking you higher than it should.&lt;/p&gt;

&lt;h2&gt;
  
  
  The SXG Tradeoff
&lt;/h2&gt;

&lt;p&gt;Website loading speed depends heavily on &lt;em&gt;how&lt;/em&gt; pages are delivered. Techniques like SXG and Speculation Rules, combined with edge caching, can dramatically improve LCP. At the same time, the very SXG mechanism that enables sub-second page loads can also introduce scenarios where performance suffers.&lt;/p&gt;

&lt;p&gt;This raises an important question: Is it acceptable to sacrifice the experience of some visitors so that others enjoy a much faster site? The answer likely depends on the ratio between those who benefit and those who are negatively affected.&lt;/p&gt;

&lt;p&gt;The key questions are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;How many visitors experience degraded performance when SXG is enabled?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What strategies can reduce or eliminate those negative effects?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When we balance the wins against the drawbacks, is SXG’s overall impact on LCP positive or negative?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’ll explore these questions in the next part of this series.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you’d like to be notified when it’s published—and get more insights on performance experiments like this—make sure to follow me or &lt;a href="https://www.pawelpokrywka.com/welcome" rel="noopener noreferrer"&gt;subscribe to my newsletter&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Thanks
&lt;/h2&gt;

&lt;p&gt;A special thanks to &lt;strong&gt;Michał Pokrywka&lt;/strong&gt; and &lt;strong&gt;Maciej Woźny&lt;/strong&gt; for their valuable comments.&lt;/p&gt;

&lt;p&gt;I'd also like to thank &lt;strong&gt;Matt Zeunert&lt;/strong&gt; and the &lt;strong&gt;DebugBear&lt;/strong&gt; team for providing me with access to their web performance monitoring service.&lt;/p&gt;

&lt;p&gt;And thank &lt;strong&gt;you&lt;/strong&gt; for reading. I hope you enjoyed it!&lt;/p&gt;

&lt;p&gt;I'm researching Signed Exchanges extensively - connect with me here or on &lt;a href="https://www.pawelpokrywka.com/" rel="noopener noreferrer"&gt;my blog&lt;/a&gt; for more insights.&lt;/p&gt;

</description>
      <category>webperf</category>
      <category>signedexchanges</category>
      <category>google</category>
      <category>seo</category>
    </item>
    <item>
      <title>15 ways your website loads from Google Search and how to measure each one</title>
      <dc:creator>Paweł Pokrywka</dc:creator>
      <pubDate>Sat, 06 Sep 2025 07:37:00 +0000</pubDate>
      <link>https://forem.com/paweldev/15-ways-your-website-loads-from-google-search-and-how-to-measure-each-one-59jg</link>
      <guid>https://forem.com/paweldev/15-ways-your-website-loads-from-google-search-and-how-to-measure-each-one-59jg</guid>
      <description>&lt;p&gt;When you find a page on Google, you probably don't think much about what happens before you click it. Perhaps you've heard about prefetching, but did you know that Google employs 5 or more methods (depending on how you classify them) for loading pages? Each technique has distinct performance characteristics.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This post is a part of a series about Signed Exchanges (SXG). In an attempt to measure how SXG impacts page loading speed I needed to distinguish between different page load types. This article is based on my research and summarizes my findings.&lt;br&gt;
SXG is a technology to make your website load faster for Google-referred users. If you want to implement it on your website, start &lt;a href="https://www.pawelpokrywka.com/p/how-i-took-lcp-down-under-350ms" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Main page load type categories
&lt;/h2&gt;

&lt;h3&gt;
  
  
  (Plain old) on-demand document loading
&lt;/h3&gt;

&lt;p&gt;Let’s start with the obvious way of loading the page. It was there from the beginning of the web.&lt;/p&gt;

&lt;p&gt;When the user clicks a link, the browser fetches the HTML. Then the browser fetches all the assets required for the document to display.&lt;/p&gt;

&lt;p&gt;It’s simple and it works. However, if the server hosting the page is slow or overloaded, the user will experience a delay, which could lead to a poor experience.&lt;/p&gt;

&lt;p&gt;To improve the situation, Google rewards websites that load quickly with better search results positions. However, Google knows that not every website in the world can become fast.&lt;/p&gt;

&lt;p&gt;That is probably one of the reasons Google implemented prefetching.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prefetching the document (Speculation Rules)
&lt;/h3&gt;

&lt;p&gt;The idea is to download the page before the user decides to click on it. When they do, the page HTML is ready.&lt;/p&gt;

&lt;p&gt;For the vast majority of Google results, prefetching is implemented using &lt;a href="https://developer.chrome.com/blog/search-speculation-rules" rel="noopener noreferrer"&gt;Speculation Rules&lt;/a&gt; and a &lt;a href="https://developer.chrome.com/blog/private-prefetch-proxy" rel="noopener noreferrer"&gt;private prefetch proxy&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This feature is supported on Chromium-based browsers, but support from other browsers may follow.&lt;/p&gt;

&lt;p&gt;HTML prefetching greatly improves user experience and works with most websites out of the box, but it comes with a limitation. It doesn’t &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Speculation_Rules_API#:~:text=handle%20subresource%20prefetches" rel="noopener noreferrer"&gt;prefetch&lt;/a&gt; &lt;a href="https://developer.chrome.com/docs/devtools/application/debugging-speculation-rules#:~:text=only%20prefetch%20the%20document" rel="noopener noreferrer"&gt;assets&lt;/a&gt; such as CSS styles, images, and fonts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prefetching the complete page (Signed Exchanges)
&lt;/h3&gt;

&lt;p&gt;It is possible to have the whole page (including assets) prefetched on Google results. When the user clicks the result, the browser starts rendering the page immediately without the need to download assets first.&lt;/p&gt;

&lt;p&gt;To achieve this, the website owner has to &lt;a href="https://www.pawelpokrywka.com/p/how-i-took-lcp-down-under-350ms" rel="noopener noreferrer"&gt;implement Signed Exchanges&lt;/a&gt; (SXG). This technology was integrated into Google search even before the HTML prefetching I described above.&lt;/p&gt;

&lt;p&gt;It’s worth noting that only Chromium-based browsers implement SXG.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerendering (Accelerated Mobile Pages)
&lt;/h3&gt;

&lt;p&gt;Probably the fastest way to display a page is to use Accelerated Mobile Pages (AMP). That’s because Google prerenders websites built using this technology, so when the user clicks, the page is not only prefetched but also fully rendered. It’s hard to imagine a better user experience.&lt;/p&gt;

&lt;p&gt;On the downside, AMP has severe restrictions. Developers are forced to use a &lt;a href="https://amp.dev/documentation/guides-and-tutorials/learn/spec/amphtml" rel="noopener noreferrer"&gt;subset of HTML&lt;/a&gt;, JavaScript is constrained, and many other &lt;a href="https://www.youtube.com/watch?v=Gv8A4CktajQ" rel="noopener noreferrer"&gt;limitations&lt;/a&gt; apply.&lt;/p&gt;

&lt;p&gt;It works only on mobile devices using Chromium-based browsers.&lt;/p&gt;

&lt;p&gt;The other problem with AMP is a centralized cache fully controlled by Google. It means Google effectively became a hosting company for every possible AMP page on the Internet. Quite dystopian, if you ask me.&lt;/p&gt;

&lt;p&gt;As AMP pages are hosted on Google, your website becomes a subpage of &lt;code&gt;google.com&lt;/code&gt;. Your URL will look like below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://www.google.com/amp/s/www-yourdomain-com/yourpage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is a way to deal with the last two problems by introducing SXG to the mix. Cloudflare even has a &lt;a href="https://www.cloudflare.com/website-optimization/amp-real-url/" rel="noopener noreferrer"&gt;switch&lt;/a&gt; for that. But when I was writing this post, I tried hard but failed to find any example of an AMP website using it.&lt;/p&gt;

&lt;h3&gt;
  
  
  On-demand loading with server-side redirection (ads)
&lt;/h3&gt;

&lt;p&gt;The last category describes how a page is loaded when the user clicks a Google ad. The document is loaded on demand, but with an additional HTTP/302 redirect for registering the click, which adds latency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No prefetching&lt;/strong&gt;. Ironically, Google’s paying customers get the worst possible page load method. If you use Google Ads, make sure your page is optimized to load fast to neutralize the latency added by Google. Another potential solution is to &lt;a href="https://support.google.com/google-ads/answer/7495018?hl=en" rel="noopener noreferrer"&gt;use AMP on your ad landing page&lt;/a&gt; for mobile users; however, I was unable to find an example in the wild.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conditions, edge cases, and quirks
&lt;/h2&gt;

&lt;p&gt;The above categories are just scratching the surface of a full list of possible ways the page can load when referred from Google. That’s because various factors can improve or degrade page load efficiency, and some page load types impact others.&lt;/p&gt;

&lt;h3&gt;
  
  
  Signed Exchanges
&lt;/h3&gt;

&lt;p&gt;When mentioning prefetching the entire page using SXG above, I described the best possible scenario: the HTML and all the required assets (or subresources) are being prefetched. That’s the goal, but in the real world, things don’t always go smoothly.&lt;/p&gt;

&lt;p&gt;Various factors can influence how the page is loaded, and they greatly impact performance. Here are the possible SXG page load types I identified:&lt;/p&gt;

&lt;h4&gt;
  
  
  Page prefetched with subresources
&lt;/h4&gt;

&lt;p&gt;If most of your SXG-enabled page views are prefetched with subresources, you did a great job optimizing your website!&lt;/p&gt;

&lt;p&gt;However, despite your efforts, you will notice other, less efficient page loads.&lt;/p&gt;

&lt;h4&gt;
  
  
  Page prefetched without subresources
&lt;/h4&gt;

&lt;p&gt;The browser will use subresources only if all of them were successfully prefetched. The &lt;a href="https://www.pawelpokrywka.com/p/understanding-cors-sxg-errors" rel="noopener noreferrer"&gt;all-or-nothing&lt;/a&gt; principle states that even if one subresource fails to prefetch, when the user visits the page, all of the subresources will need to be downloaded again.&lt;/p&gt;

&lt;p&gt;Here are the causes of missing subresources I identified:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;SXG implementation errors (should not happen if you followed my &lt;a href="https://www.pawelpokrywka.com/p/fixing-sxg-prefetching-errors-caused-by-mutable-subresources" rel="noopener noreferrer"&gt;previous&lt;/a&gt; &lt;a href="https://www.pawelpokrywka.com/p/debugging-complex-signed-exchanges-subresource-issue" rel="noopener noreferrer"&gt;SXG&lt;/a&gt; &lt;a href="https://www.pawelpokrywka.com/p/other-errors-with-signed-exchanges" rel="noopener noreferrer"&gt;posts&lt;/a&gt; carefully)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Global Google SXG cache issues (very rare)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Google SXG cache housekeeping and temporary errors (happens regularly, but impacts only a small portion of page loads)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The user not spending enough time on the search results page for the prefetching to complete&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;User opening the page in a new tab (more on this later)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As you can see, apart from the first, the remaining factors are beyond your control.&lt;/p&gt;

&lt;p&gt;But still, at least the HTML was prefetched, and it’s a win for performance when compared to vanilla, on-demand page load.&lt;/p&gt;

&lt;h4&gt;
  
  
  Page loaded on-demand using client-side redirect fallback
&lt;/h4&gt;

&lt;p&gt;Sometimes the target page is unavailable in Google SXG cache. The browser will still try to prefetch it (it signals Google to populate the SXG cache with this page when possible), but will fail.&lt;/p&gt;

&lt;p&gt;When the user finally decides to click the result, the browser will once again try to load the cached page from the SXG cache. The cache response will include a simple HTML document with a client-side JavaScript redirect. The browser will follow this redirect, loading the target page.&lt;/p&gt;

&lt;p&gt;It’s worth noting, the redirect document introduces additional latency caused by another HTTP request, HTML parsing, and JavaScript execution.&lt;/p&gt;

&lt;h4&gt;
  
  
  Page loaded on-demand from Google SXG cache
&lt;/h4&gt;

&lt;p&gt;Sometimes the browser doesn’t prefetch the page, but loads its SXG version on demand when the user navigates to it. I found 2 reasons for that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Google prefetches only 1 SXG result on a page. I don’t know how Google determines which SXG result to prefetch, but it’s not always the first result for sure. If the user chooses to click the one that was not prefetched, it is loaded on demand, but still via SXG.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Google tried to prefetch the SXG page and failed. However, the user stayed long enough for the Google SXG cache to become populated. Now, when the user clicks the result, it’s loaded—on demand—from the SXG cache instead of the fallback document mentioned above.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Loading the SXG version of the page on demand introduces cryptography overhead. I suspect it comes mostly from additional requests required to download certificates for signature verification. I don’t think the CPU overhead plays a big role, because cryptography operations are cheap nowadays.&lt;/p&gt;

&lt;h3&gt;
  
  
  Speculation Rules
&lt;/h3&gt;

&lt;p&gt;Currently, Google prefetches the page with Speculation Rules if all of the following conditions apply:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The target page is in the top 2 results, or the user hovered over the result (on desktop only).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The browser doesn’t hold any cookies for the target website. In most cases, this means the user hasn't visited the site before.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The device has enough capacity in terms of memory, network, and battery. For example, using battery-saving mode on a mobile will deactivate prefetching.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Prefetching is not disabled by browser extensions; for example, &lt;a href="https://chromewebstore.google.com/detail/privacy-badger/pkehgijcmpdhfbdbbnkijodmdjhbjlgp" rel="noopener noreferrer"&gt;Privacy Badger&lt;/a&gt; by default disables prefetching.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The target website doesn’t disallow prefetching (by default, prefetching is allowed).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s worth noting that none of the above conditions apply to SXG prefetching.&lt;/p&gt;

&lt;h3&gt;
  
  
  AMP prerendering
&lt;/h3&gt;

&lt;p&gt;Similarly to SXG, only one of the AMP results is prerendered. Others need to be loaded on demand from the AMP cache.&lt;/p&gt;

&lt;p&gt;The AMP viewer is shared between the prerendered page and the others; therefore, it loads instantly every time. However, the user needs to wait for the non-prerendered pages to load in the viewer.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There are likely other edge cases when loading AMP pages, such as missing AMP cache entries during prerendering or on-demand loading.&lt;br&gt;
Replicating them manually would require creating test pages, waiting for Google to index them, searching for them in Google, and hoping that edge cases manifest themselves.&lt;br&gt;
I couldn’t find any tools that would make it less difficult and time-consuming. Therefore, I chose not to research these cases. If you have additional information on this topic, please leave a comment below.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Opening the page in a new tab
&lt;/h3&gt;

&lt;p&gt;Most of the time, when the user opens the page in a new tab, it is loaded in the background. The user will likely switch to the tab after some time, giving it a chance to render fully, while they are still interacting with the referrer website. Therefore, I believe the page load speed is far less critical in these cases. Your performance optimizations are aimed mostly at people opening the page in an existing tab.&lt;/p&gt;

&lt;p&gt;However, it’s good to know that opening the page in a new tab degrades performance:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Already prefetched SXG subresources are not used as mentioned previously.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The prefetched SXG HTML is also discarded, unless the CTRL+click method is used (only on desktop). I explained why in the &lt;a href="https://www.pawelpokrywka.com/p/other-errors-with-signed-exchanges" rel="noopener noreferrer"&gt;previous part&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AMP pages have to be loaded on demand. No prerendering or prefetching.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On the other hand, Speculation Rules prefetching handles new tabs extremely reliably. It just works, regardless of how the tab is opened, on both desktop and mobile.&lt;/p&gt;

&lt;h3&gt;
  
  
  Duplicate prefetching
&lt;/h3&gt;

&lt;p&gt;If the given page implements SXG, Google prefetches its SXG version, but at the same time uses Speculation Rules prefetching for the normal version. In my tests, I observed this phenomenon on the desktop only.&lt;/p&gt;

&lt;p&gt;Seems like a waste of the user’s bandwidth (it is!), but it has a bright side too. If the user decides to open the page in a new tab (using a right-click menu, not CTRL+click), then the prefetched SXG version is discarded, but the normal version prefetched with Speculation Rules is used instead. Subresources have to be loaded normally, but at least the document is ready quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generic techniques for improving page load speed
&lt;/h2&gt;

&lt;p&gt;If you want to compare various methods used to load a page when referred from Google, you should also consider techniques that improve performance and are not specific to Google.&lt;/p&gt;

&lt;p&gt;You should segment your results by the technique applied for a given page load. That’s because each page load may use a different set of techniques. Mixing high- and low-performance loads in measurements increases the variance of the results, thus making it harder to analyze.&lt;/p&gt;

&lt;h3&gt;
  
  
  Early Hints
&lt;/h3&gt;

&lt;p&gt;Early Hints allow the browser to begin fetching subresources even before the main document starts to load. This can improve the performance, especially for the pages that take time to render on the server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Caching at the edge
&lt;/h3&gt;

&lt;p&gt;If you cache HTML at the edge, such as when using Cloudflare cache, then it should be measured as a different category of page loads for the same reason as above.&lt;/p&gt;

&lt;p&gt;If the page is delivered from the edge cache, it results in:&lt;/p&gt;

&lt;p&gt;Subresources start loading immediately, especially if they were listed in the &lt;code&gt;Link&lt;/code&gt; header of the response. The benefits are similar to Early Hints.&lt;/p&gt;

&lt;p&gt;HTML becomes available much earlier than if it had to be delivered from the origin.&lt;/p&gt;

&lt;p&gt;The cached page can use Early Hints, but I don’t see any performance benefits.&lt;/p&gt;

&lt;h3&gt;
  
  
  No impact on prefetching
&lt;/h3&gt;

&lt;p&gt;The above page load types affect only the on-demand category of page loads. If the page is prefetched, it doesn’t matter if it was served with Early Hints or using edge cache.&lt;/p&gt;

&lt;h3&gt;
  
  
  Browser caching
&lt;/h3&gt;

&lt;p&gt;If the user has visited the given website in the past, when they visit it again, the browser cache may contain some subresources, such as the website logo, ready to be used. If the website uses client-side HTML-caching, even the document could be saved in the browser cache, making subsequent visits instant.&lt;/p&gt;

&lt;p&gt;When measuring the performance of various page loads, cached visits should be separated into a different category. Later, it may be included or excluded from the analysis.&lt;/p&gt;

&lt;p&gt;Personally, I exclude it because returning users behave differently and may be less sensitive to page load speed because they know the site already.&lt;/p&gt;

&lt;h2&gt;
  
  
  Probably an incomplete list of page load types
&lt;/h2&gt;

&lt;p&gt;Combining all the scenarios described above results in the following list of page load types. The list excludes scenarios involving a returning user and opening the page in a new tab, as those are not very useful in performance analysis.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Server Load&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Server Load with Early Hints&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Edge Cache Load&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Speculation Rules Prefetch&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SXG Prefetch with Subresources&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SXG Prefetch without Subresources&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SXG On-Demand Load&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Server Load via SXG Redirect&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Server Load with Early Hints via SXG Redirect&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Edge Cache Load via SXG Redirect&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AMP Prerender&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AMP On-Demand Load&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Server Load via Ad Redirect&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Server Load with Early Hints via Ad Redirect&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Edge Cache Load via Ad Redirect&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s a lot of possibilities! I started preparing a flowchart describing the conditions needed for each page load type. However, it quickly became too complex, so I ditched this idea.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison of various page load types
&lt;/h2&gt;

&lt;p&gt;At first, I thought to sort all the load types by the speed, measured by Largest Contentful Paint (LCP), that they should (hypothetically) offer. It’s easy for top performers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;AMP Prerender (fastest)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SXG Prefetch with Subresources&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Speculation Rules Prefetch&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SXG Prefetch without Subresources (slowest)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;strong&gt;AMP Load&lt;/strong&gt; is very hard to grade because it’s totally different and potentially leaner than a full page. Therefore, it may, in some conditions, load even faster than the full page with a prefetched HTML. On the other hand, if the prefetched page is optimized, &lt;strong&gt;AMP Load&lt;/strong&gt; will load more slowly.&lt;/p&gt;

&lt;p&gt;In the on-demand category, there are various ways to fetch data. I sorted them by speed:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Edge Cache Load (fastest)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Server Load with Early Hints&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Server Load (slowest)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But there is one more - &lt;strong&gt;SXG On-Demand Load&lt;/strong&gt;. It’s challenging to rank due to the SXG overhead and other individual factors, such as your server speed and connectivity.&lt;/p&gt;

&lt;p&gt;The other dimension is the redirection method used. It may be ordered like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;No redirection (fastest).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Server-side redirection (Google Ads).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Client-side redirection (SXG fallback, slowest).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How to measure different page load types in real life
&lt;/h2&gt;

&lt;p&gt;Now, when you understand the difference between various page load types, it’s time to measure how they compare in terms of performance under real-world conditions. I will show you how I did it on my website.&lt;/p&gt;

&lt;p&gt;The first thing that’s needed is a method to differentiate page load types. I created a JavaScript library &lt;a href="https://github.com/pepawel/page-load-type" rel="noopener noreferrer"&gt;page-load-type&lt;/a&gt; just for that.&lt;/p&gt;

&lt;p&gt;It uses a variety of techniques to determine how the page was loaded and supports most of the load types described in this post, except AMP and Google Ads (both were not used on my website during the measurement).&lt;/p&gt;

&lt;h3&gt;
  
  
  Requirements
&lt;/h3&gt;

&lt;p&gt;Measuring every page load doesn’t make sense. The visit should be collected only when all of the following requirements are met:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The user comes from Google. It’s easy to overlook a special case involving the SXG cache. Read on.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The browser cache doesn’t contain entries for the measured website. Otherwise, some assets may be fetched from the cache, which improves load time, but pollutes the measurement data, making it overly optimistic.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The page isn’t opened in a new tab. As discussed earlier in this post, it breaks some prefetching/prerendering methods, and at the same time, decreases the performance sensitivity of the user.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the website, such as mine, implements SXG, additional requirements follow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The current page supports SXG. It is perfectly fine if some of your pages, particularly non-performance-critical ones, don’t support SXG. Examples include Terms and Conditions and Privacy Policy. Make sure to insert the JavaScript measurement code only on pages with SXG implemented.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The browser supports SXG. It doesn’t make sense to include Firefox visits, for example, because &lt;a href="https://github.com/mozilla/standards-positions/issues/29" rel="noopener noreferrer"&gt;Mozilla doesn’t like SXG&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Detecting Google-referred visits
&lt;/h4&gt;

&lt;p&gt;To determine if a user is Google-referred, the easiest method is to check the referrer for Google domains. However, as I wrote &lt;a href="https://www.pawelpokrywka.com/p/understanding-cors-sxg-errors" rel="noopener noreferrer"&gt;earlier&lt;/a&gt;, when the SXG fallback is used, the referrer is set to the Google SXG cache (&lt;code&gt;your-domain-com.webpkgcache.com&lt;/code&gt;). Taking everything into account, the final JavaScript function could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isFromGoogle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;referrer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;regex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;^|&lt;/span&gt;&lt;span class="se"&gt;\.)((&lt;/span&gt;&lt;span class="sr"&gt;google&lt;/span&gt;&lt;span class="se"&gt;\.[&lt;/span&gt;&lt;span class="sr"&gt;a-z&lt;/span&gt;&lt;span class="se"&gt;]{2,3}(?:\.[&lt;/span&gt;&lt;span class="sr"&gt;a-z&lt;/span&gt;&lt;span class="se"&gt;]{2})?)&lt;/span&gt;&lt;span class="sr"&gt;|&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;webpkgcache&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;com&lt;/span&gt;&lt;span class="se"&gt;))&lt;/span&gt;&lt;span class="sr"&gt;$/i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;referrer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;referrer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;referrer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Checking the browser cache
&lt;/h4&gt;

&lt;p&gt;The simplest way to ensure the cache is empty is to check whether the visitor is accessing the page for the first time.&lt;/p&gt;

&lt;p&gt;In most cases, this can be done by checking for the presence of a cookie or local storage entry and then immediately setting it for future visits&lt;sup id="fnref1"&gt;1&lt;/sup&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isFirstVisit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;returning&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;returning&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;returning&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;returning&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Detecting a new tab
&lt;/h4&gt;

&lt;p&gt;When navigating between pages, the browser maintains the history of visited pages. However, when the page is opened in a new tab (or new window), the history is not preserved. This browser behavior can be used to detect how the page was opened.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isOpenedInNewTab&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;history&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  SXG support in the browser
&lt;/h4&gt;

&lt;p&gt;I couldn’t find a direct way to query the browser for SXG support from JavaScript. As the SXG is implemented only in Chromium, the naive approach is to parse the &lt;code&gt;User Agent&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;However, on iOS devices, Chromium uses WebKit, which doesn’t support SXG. Also, some Chromium-based browsers, such as Brave, &lt;a href="https://github.com/brave/brave-browser/issues/24227" rel="noopener noreferrer"&gt;intentionally&lt;/a&gt; disable SXG. In some cases, the browser may spoof the &lt;code&gt;User Agent&lt;/code&gt; to look like Google Chrome. All of this makes it impossible to reliably detect SXG support by parsing the &lt;code&gt;User Agent&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Accept header to the rescue
&lt;/h4&gt;

&lt;p&gt;When loading a page, the browser includes the &lt;code&gt;Accept&lt;/code&gt; header in the request. It contains the &lt;code&gt;application/signed-exchange&lt;/code&gt; substring if the browser supports SXG.&lt;/p&gt;

&lt;p&gt;However, the Accept header is accessible only on the server. Therefore, the detection of SXG support should be implemented in the app or some form of middleware. Probably one of the simplest methods is to include a server-side generated &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag near the top of the HTML, looking like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"sxgSupportScript"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sxgSupport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The global &lt;code&gt;sxgSupport&lt;/code&gt; constant can be accessed later in the frontend code.&lt;/p&gt;

&lt;h4&gt;
  
  
  HTML caching introduces a challenge
&lt;/h4&gt;

&lt;p&gt;Things become more complex when HTML caching on the edge becomes involved. Let’s say your server generated an HTML with the &lt;code&gt;sxgSupport&lt;/code&gt; constant set to &lt;code&gt;true&lt;/code&gt;, which was correct for the &lt;strong&gt;Chrome&lt;/strong&gt; browser requesting the page at this moment. But the page has been cached, the &lt;code&gt;sxgSupport&lt;/code&gt; is frozen to &lt;code&gt;true&lt;/code&gt;, and when &lt;strong&gt;Firefox&lt;/strong&gt; requests the page, it gets the incorrect value of &lt;code&gt;sxgSupport&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The solution is to set the sxgSupport constant &lt;em&gt;after&lt;/em&gt; it’s retrieved from the cache. It’s a perfect task for a Cloudflare worker, which could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Get the original response&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Check the SXG support&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;acceptHeader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;accept&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;supportsSXG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;acceptHeader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/signed-exchange&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Only process HTML responses&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contentType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Create HTMLRewriter to modify the script element&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rewriter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HTMLRewriter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;script#sxgSupportScript&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// Replace the entire content of the script tag&lt;/span&gt;
          &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`window.sxgSupport = &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;supportsSXG&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Apply the rewriter and return the modified response&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;rewriter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// For more information, see the following blog post:&lt;/span&gt;
    &lt;span class="c1"&gt;// https://www.pawelpokrywka.com/p/different-methods-of-prefetching&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the instructions on how to set up and deploy Cloudflare workers, see my &lt;a href="https://www.pawelpokrywka.com/p/subresources-prefetching-with-signed-exchanges" rel="noopener noreferrer"&gt;earlier blog post&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Including the page load type in the measurements
&lt;/h3&gt;

&lt;p&gt;If you implemented all of the above and properly installed the &lt;a href="https://github.com/pepawel/page-load-type" rel="noopener noreferrer"&gt;page-load-type&lt;/a&gt; library in your project, the measurement code should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// If you use SXG, this code should be run only on SXG-enabled pages.&lt;/span&gt;
&lt;span class="c1"&gt;// If you don't use SXG you can skip checking for window.sxgSupport.&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;getPageLoadType&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;page-load-type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isFromGoogle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="nf"&gt;isFirstVisit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isOpenedInNewTab&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sxgSupport&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loadType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getPageLoadType&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Run measurement code when loadType becomes available&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For LCP measurement, I used DebugBear, a frontend performance monitoring tool. I added the DebugBear snippet to the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; section and implemented page load type tracking. Here is the resulting code, with boring fragments replaced by &lt;code&gt;[...]&lt;/code&gt; marks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;[...]&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isFromGoogle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;[...])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loadType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getPageLoadType&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Record page load type&lt;/span&gt;
  &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dbbRum&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tag1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;loadType&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Shortly after deployment, I began receiving performance reports for each qualifying Google-referred visit, along with the page load type.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl90fzkzc7ohpgg1mew6n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl90fzkzc7ohpgg1mew6n.png" width="800" height="424"&gt;&lt;/a&gt;&lt;/p&gt;
Page view details as presented in DebugBear UI. As you can see, I track more dimensions than page load type, but that’s irrelevant to this article.



&lt;p&gt;I measured user engagement metrics in Google Analytics by firing a custom event within the same &lt;code&gt;if&lt;/code&gt; condition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;gtag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;event&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;google_visit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;page_load_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;loadType&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After creating a &lt;a href="https://support.google.com/analytics/answer/14239696" rel="noopener noreferrer"&gt;custom, event-based dimension&lt;/a&gt; and waiting 24 hours, I was able to use the page load type in my reports.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;You learned about the various methods Google uses to load your page, how to differentiate between them, and how to set up performance measurement.&lt;/p&gt;

&lt;p&gt;Performance measurements will vary across websites, but seeing a real-life case study can help you decide whether and how SXG could improve your site's performance.&lt;/p&gt;

&lt;p&gt;Therefore, I'll present the results for my website in the upcoming part of the series.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thanks
&lt;/h2&gt;

&lt;p&gt;Thank you for reading this post. I hope you found it useful.&lt;/p&gt;

&lt;p&gt;I'm researching Signed Exchanges extensively - connect with me here or on &lt;a href="https://www.pawelpokrywka.com/" rel="noopener noreferrer"&gt;my blog&lt;/a&gt; for more insights.&lt;/p&gt;







&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;If the user clears browser storage and returns to the page, this method will falsely indicate that it's their first visit. The same will happen if the browser &lt;a href="https://webkit.org/blog/10218/full-third-party-cookie-blocking-and-more/" rel="noopener noreferrer"&gt;clears its storage by itself&lt;/a&gt;. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>webperf</category>
      <category>signedexchanges</category>
      <category>google</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
