<?xml version="1.0" encoding="UTF-8" standalone="no"?><rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" version="2.0">

<channel>
	<title>Mobiletech</title>
	<atom:link href="https://www.mobiletech.mobi/feed/" rel="self" type="application/rss+xml"/>
	<link>https://www.mobiletech.mobi</link>
	<description></description>
	<lastBuildDate>Sat, 29 Aug 2026 18:11:17 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1</generator>
	<xhtml:meta content="noindex" name="robots" xmlns:xhtml="http://www.w3.org/1999/xhtml"/><item>
		<title>The Web Page No Longer Has a Reliable End</title>
		<link>https://www.mobiletech.mobi/web-page-lifecycle-unload-bfcache-visibility/</link>
		
		<dc:creator><![CDATA[mobiletech_admin]]></dc:creator>
		<pubDate>Sat, 29 Aug 2026 10:00:00 +0000</pubDate>
				<category><![CDATA[Mobile Web]]></category>
		<guid isPermaLink="false">http://localhost:18082/web-page-lifecycle-unload-bfcache-visibility/</guid>

					<description><![CDATA[How pages should handle visibility changes, suspension, bfcache restoration and termination without relying on one final unload callback.]]></description>
										<content:encoded><![CDATA[<div class="mt-article">
<div class="mt-article__intro">
<p class="mt-article__lede">A web page used to look like a small program with a reassuringly linear life. It loaded, it ran, and eventually it unloaded. That last step appeared to give developers a place to save a draft, send analytics, close a connection or mark a session as finished.</p>
<p>Mobile operating systems made that story difficult to defend. A browser can move into the background, have its processes suspended, and later lose them under memory pressure without ever returning control to the page. Back/forward caches complicate the model in another direction: navigating away may not destroy the document at all. The browser may freeze the whole page and bring it back with its DOM and JavaScript state intact.</p>
<p>Chrome’s current deprecation of <code>unload</code> matters, but mostly because it formalizes what mobile developers have had to learn for years: an application cannot assume that it will observe its own termination.</p>
<p>That constraint now belongs to <a href="/mobile-services/">the modern mobile web platform</a>. For the earlier strategic context, <a href="/blog/mobile-web-vs-native-apps-revisited/11672/">the archive’s native-versus-web application-platform debate</a> shows how MobileTech framed reach and distribution before lifecycle resilience became a first-class concern.</p>
</p></div>
<aside class="mt-callout mt-callout--current" aria-labelledby="chrome-unload-deprecation-title">
<h2 id="chrome-unload-deprecation-title" class="mt-callout__title">Chrome’s staged unload deprecation</h2>
<p>Chrome’s published schedule placed the staged deprecation at 80% of Chrome page loads across all sites on August 25, 2026, with 100% planned for September 22. The percentage refers to page loads, not sites or users, and Chrome warns that milestones may change. The API has not been physically removed: Chrome is changing the default so that handlers do not fire unless a site explicitly opts back in.<a class="mt-citation" href="#source-1" aria-label="Source 1">[1]</a><a class="mt-citation" href="#source-2" aria-label="Source 2">[2]</a></p>
</aside>
<nav class="mt-toc" aria-label="Article contents">
<h2 class="mt-toc__title">Contents</h2>
<ol class="mt-toc__list">
<li><a href="#why-unload-made-sense">Why <code>unload</code> once made sense</a></li>
<li><a href="#mobile-broke-the-model">Mobile broke the terminal-callback model</a></li>
<li><a href="#bfcache">Back/forward cache made “leaving” non-terminal</a></li>
<li><a href="#modern-signals">The modern signals: visibility, pagehide and pageshow</a></li>
<li><a href="#page-lifecycle-model">There is no single interoperable Page Lifecycle API</a></li>
<li><a href="#analytics">Analytics after <code>unload</code></a></li>
<li><a href="#state-persistence">State persistence and <code>beforeunload</code></a></li>
<li><a href="#resource-cleanup">Resource cleanup without a final callback</a></li>
<li><a href="#browser-differences">Browser differences still matter</a></li>
<li><a href="#chrome-rollout">What Chrome is changing in 2026</a></li>
<li><a href="#audit-migration">How to audit an unload dependency</a></li>
</ol>
</nav>
<section id="why-unload-made-sense" class="mt-article__section">
<h2>Why <code>unload</code> once made sense</h2>
<p>The original document model was easy to understand. A new document replaced the old one. <code>load</code> was a setup point; <code>unload</code> was a teardown point. On desktop browsers, where a visible page often remained attached to a long-lived browser process until a deliberate navigation or window close, that model worked often enough to become habit.</p>
<p>Developers used the event for several very different jobs:</p>
<ul>
<li>sending end-of-visit analytics;</li>
<li>saving form values, draft text or application state;</li>
<li>closing sockets and other resources;</li>
<li>telling a server that a session had ended;</li>
<li>performing library or component cleanup.</li>
</ul>
<p>The problem was not that all of these needs were imaginary. The problem was that they were bundled behind one ambiguous idea: “the user is leaving.”</p>
<p>Leaving can mean a same-tab navigation, a reload, closing one tab, closing the browser, switching to another app, losing the browser process, or having the operating system kill that process later. Early web code routinely treated these as variations of the same transition. They are not.</p>
<figure class="mt-figure mt-figure--wide">
      <img decoding="async" class="mt-figure__image" src="/wp-content/themes/mobiletech-heritage/assets/page-lifecycle/lifecycle-shift.svg" width="1280" height="720" loading="lazy" alt="Comparison between the old linear page lifecycle and a modern branching lifecycle with hidden, frozen, restored and discarded states."><figcaption>The old mental model had one terminal edge. The modern model has several possible transitions, including paths that produce no final script callback.</figcaption></figure>
<p><code>unload</code> therefore offered a practical behavior and an architectural illusion at the same time. It encouraged application logic to wait until the last possible moment, even though that moment was never under the page’s control.</p>
</section>
<section id="mobile-broke-the-model" class="mt-article__section">
<h2>Mobile broke the terminal-callback model</h2>
<p>A phone cannot keep every background application fully alive. Memory is limited, battery is limited, and the operating system has stronger priorities than preserving a callback for a hidden browser tab. Android documents that cached and background processes may be killed to reclaim memory; Apple’s lifecycle guidance likewise treats background execution and later suspension or termination as normal resource-management behavior.<a class="mt-citation" href="#source-16" aria-label="Source 16">[16]</a><a class="mt-citation" href="#source-17" aria-label="Source 17">[17]</a></p>
<p>Consider a common sequence:</p>
<ol>
<li>A user opens a page and starts writing.</li>
<li>The user switches to a messaging or camera app.</li>
<li>The browser moves into the background.</li>
<li>Memory pressure increases later.</li>
<li>The operating system removes the browser process.</li>
</ol>
<p>There is no useful instant between steps four and five when the hidden page can be woken up and politely told to finish its JavaScript. Once the process is gone, it is gone. <code>unload</code>, <code>beforeunload</code> and even <code>pagehide</code> may never run in this scenario.<a class="mt-citation" href="#source-12" aria-label="Source 12">[12]</a><a class="mt-citation" href="#source-13" aria-label="Source 13">[13]</a></p>
<p>This was sometimes described as mobile browsers being unreliable. A more useful reading is that mobile browsers exposed a systems principle that desktop web development had managed to ignore: no application controls its own termination.</p>
<p>The same pressure is no longer confined to phones. Desktop browsers suspend background work, freeze eligible tabs and discard documents to reduce CPU, memory and energy use. Mobile did not create every lifecycle mechanism now used on the web, but constrained devices made the old assumption fail earlier and more visibly.<a class="mt-citation" href="#source-9" aria-label="Source 9">[9]</a></p>
<aside class="mt-callout" aria-label="Key distinction">
<p><strong>A missing exit callback is not necessarily a browser bug.</strong> It may be the consequence of process suspension, process termination or a deliberate decision to preserve the page for later restoration.</p>
</aside>
</section>
<section id="bfcache" class="mt-article__section">
<h2>Back/forward cache made “leaving” non-terminal</h2>
<p>Back/forward cache, usually shortened to bfcache, is not the HTTP cache. The HTTP cache stores responses and resources that can be reused during a new load. Bfcache keeps an entire document alive in memory: the DOM, JavaScript heap and much of the page’s runtime state are preserved while execution is paused. A history navigation can then restore that page instead of rebuilding it from HTML, CSS and JavaScript.<a class="mt-citation" href="#source-15" aria-label="Source 15">[15]</a></p>
<p>This is an old idea. WebKit says its Page Cache was built in 2002, before the first Safari beta. Mozilla documented whole-page in-memory caching, including preserved JavaScript state, in Firefox 1.5. WebKit added <code>pageshow</code> and <code>pagehide</code> in 2009, explicitly crediting Mozilla’s earlier solution.<a class="mt-citation" href="#source-4" aria-label="Source 4">[4]</a><a class="mt-citation" href="#source-5" aria-label="Source 5">[5]</a><a class="mt-citation" href="#source-6" aria-label="Source 6">[6]</a></p>
<p>The mechanism creates an unavoidable conflict with <code>unload</code>.</p>
<p>If the browser fires <code>unload</code> before placing a page in bfcache, the handler may perform destructive teardown: close connections, remove state, detach components or tell a server that the session is over. The page is then restored in a state its own code declared terminal.</p>
<p>If the browser does not fire <code>unload</code>, the page can be cached safely, but the callback is no longer dependable. Firing it later, when an invisible cached page is finally evicted, would mean unexpectedly waking old script that the user left minutes ago. WebKit’s engineers described this exact dilemma in 2009. The core conflict has not changed.<a class="mt-citation" href="#source-5" aria-label="Source 5">[5]</a></p>
<figure class="mt-figure mt-figure--wide">
      <img decoding="async" class="mt-figure__image" src="/wp-content/themes/mobiletech-heritage/assets/page-lifecycle/bfcache-cycle.svg" width="1280" height="720" loading="lazy" alt="A back-forward cache cycle showing Page A being hidden and frozen while Page B is visible, then restored with pageshow."><figcaption>With bfcache, navigation can mean “pause this document” rather than “destroy this document.” Code that runs on the way out may need a matching restoration path.</figcaption></figure>
<p>Browsers historically resolved the conflict in different ways. Some excluded pages with <code>unload</code> listeners from bfcache. Others prioritized bfcache and skipped the handler on relevant navigations. Either choice weakens the idea that <code>unload</code> is both reliable and compatible with fast history restoration.</p>
<p>A Chromium Permissions Policy explainer reported internal telemetry in which an unload handler somewhere in the frame tree was the only bfcache blocker for 16% of history navigations. That is vendor telemetry from a particular implementation, not a measurement of the whole web, but it shows why removing an apparently harmless listener can affect a meaningful share of return navigations.<a class="mt-citation" href="#source-20" aria-label="Source 20">[20]</a></p>
<p>The performance benefit is direct. A restored page does not have to repeat its normal network, parse, style, layout and JavaScript startup work. That can make back and forward navigation feel nearly immediate, which matters particularly on mobile hardware and variable networks.</p>
</section>
<section id="modern-signals" class="mt-article__section">
<h2>The modern signals: visibility, <code>pagehide</code> and <code>pageshow</code></h2>
<p>The platform did not replace <code>unload</code> with one equally final event. It split the problem into signals that answer narrower questions.</p>
<h3>Page Visibility: can the user currently see this document?</h3>
<p><code>document.visibilityState</code> distinguishes at least <code>visible</code> and <code>hidden</code>, and <code>visibilitychange</code> fires when that state changes. A page can become hidden because the user changed tabs, minimized the browser, navigated, locked the screen or switched away from the browser app.</p>
<p>On mobile, the transition to <code>hidden</code> is often the last state change a page can reliably observe. That makes it a good checkpoint for saving important user state and flushing non-blocking analytics.<a class="mt-citation" href="#source-7" aria-label="Source 7">[7]</a><a class="mt-citation" href="#source-9" aria-label="Source 9">[9]</a></p>
<p>But <code>hidden</code> does not mean “about to die.” The user may return a second later. The page may remain hidden for hours. It may be frozen, cached, discarded or left alone. Visibility is a signal about presentation, not a termination guarantee.</p>
<pre class="mt-code" aria-label="Visibility-aware persistence example"><code>function checkpoint() {
  persistDraft();
  flushAnalytics();
}

document.addEventListener('visibilitychange', () =&gt; {
  if (document.visibilityState === 'hidden') {
    checkpoint();
  }
});

window.addEventListener('pagehide', checkpoint);</code></pre>
<p>Both events may fire during the same navigation, so the work should be idempotent. The goal is not to predict the exact end of a session. It is to move important work earlier, while execution is still available.</p>
<h3><code>pagehide</code>: is this document being left through navigation?</h3>
<p><code>pagehide</code> is navigation-aware. It can fire when a document is replaced, reloaded or closed, and it is compatible with bfcache. Its <code>persisted</code> property tells the page whether the user agent may preserve the document for reuse. In the current HTML definition, <code>true</code> means the page might be reused if it remains salvageable; it is not a promise that the user will return.<a class="mt-citation" href="#source-3" aria-label="Source 3">[3]</a></p>
<p>It is useful when the application needs to distinguish a navigation away from a mere tab switch. It is still not a universal “last chance” event: a page that goes into the background and is killed later may never receive it.<a class="mt-citation" href="#source-13" aria-label="Source 13">[13]</a></p>
<h3><code>pageshow</code>: was this document restored?</h3>
<p><code>pageshow</code> runs on the initial presentation and after a bfcache restoration. When <code>event.persisted</code> is <code>true</code>, the page is returning with preserved runtime state rather than starting from a normal reload.</p>
<pre class="mt-code" aria-label="Back-forward cache restoration example"><code>window.addEventListener('pagehide', () =&gt; {
  pauseLiveUpdates();
});

window.addEventListener('pageshow', event =&gt; {
  if (event.persisted) {
    refreshTimeSensitiveData();
  }

  resumeLiveUpdates();
});</code></pre>
<p>This restoration path is easy to overlook. A page can return with old timestamps, stale authentication assumptions, an expired API result or a connection that no longer exists. The correct response is usually selective revalidation, not a forced reload that throws away the benefit of bfcache. At product level, this is a <a href="/mobile-ux/">mobile UX problem of interruption and recovery</a> as much as a browser API problem.</p>
</section>
<section id="page-lifecycle-model" class="mt-article__section">
<h2>There is no single interoperable Page Lifecycle API</h2>
<p>“Page lifecycle” is useful terminology, but it can imply more standardization than the platform currently provides.</p>
<p>Some pieces are established web-platform APIs: Page Visibility, <code>pagehide</code>, <code>pageshow</code>, <code>beforeunload</code>, <code>unload</code>, Beacon and Fetch. The HTML Living Standard defines the page-transition events and their <code>persisted</code> state.<a class="mt-citation" href="#source-3" aria-label="Source 3">[3]</a></p>
<p>The familiar state diagram containing <em>active</em>, <em>passive</em>, <em>hidden</em>, <em>frozen</em>, <em>terminated</em> and <em>discarded</em> is largely associated with Chrome guidance and the WICG Page Lifecycle proposal. It is a valuable model of browser behavior, but not one fully interoperable API that applications can use identically in every engine.<a class="mt-citation" href="#source-9" aria-label="Source 9">[9]</a><a class="mt-citation" href="#source-10" aria-label="Source 10">[10]</a></p>
<p>That distinction matters. Chrome exposes lifecycle-related mechanisms such as <code>freeze</code>, <code>resume</code> and <code>document.wasDiscarded</code>. Developers can progressively enhance around them, but should not build a cross-browser correctness requirement on Chromium-only hooks.</p>
<aside class="mt-callout" aria-label="Lifecycle model warning">
<p><strong>Use lifecycle states as a model, not as a promise.</strong> The interoperable baseline is a collection of narrower signals. OS process management remains outside JavaScript’s control.</p>
</aside>
</section>
<section id="analytics" class="mt-article__section">
<h2>Analytics after <code>unload</code></h2>
<p>Analytics helped make exit handlers sticky. An end-of-session batch seemed efficient, and blocking the next navigation with synchronous XHR improved delivery rates. It also delayed the user, consumed scarce mobile resources and still could not solve process termination.</p>
<p>The Beacon API was designed for small, asynchronous, non-blocking reports. The user agent queues a request with Fetch’s <code>keepalive</code> flag, and the specification tells it to schedule pending beacons when the document becomes hidden. <code>navigator.sendBeacon()</code> returns <code>true</code> when the data was accepted into the queue, not when the server received it.<a class="mt-citation" href="#source-11" aria-label="Source 11">[11]</a></p>
<pre class="mt-code" aria-label="Analytics beacon example"><code>function flushAnalytics() {
  const events = readPendingEvents();

  if (events.length === 0) {
    return;
  }

  const body = JSON.stringify(events);
  navigator.sendBeacon('/analytics', body);
}</code></pre>
<p>That last distinction is important. Beacon improves the chance that a small report can continue without blocking navigation, but it is not a guaranteed delivery system. A device can lose connectivity. A process can be killed before a request is queued. The browser can reject an oversized batch. The server can fail after the client has moved on.</p>
<p><code>fetch()</code> with <code>keepalive: true</code> is the more flexible option when code needs custom headers, methods or other Fetch behavior. It is still subject to normal Fetch and CORS rules, and keepalive request bodies are deliberately limited; current Fetch semantics cap the aggregate queued body size at 64 KiB.<a class="mt-citation" href="#source-18" aria-label="Source 18">[18]</a></p>
<p>For ordinary analytics, a sensible pattern is to send data periodically or after meaningful events, then make one last best-effort flush on <code>visibilitychange</code> when the page becomes hidden. For information that the product cannot afford to lose—a payment, a saved document, a security action—the application should obtain an explicit server acknowledgement while it is active. Analytics transport is not a transaction protocol.</p>
</section>
<section id="state-persistence" class="mt-article__section">
<h2>State persistence and the narrow role of <code>beforeunload</code></h2>
<p>User state should not accumulate in memory until a theoretical exit. Drafts and client-side edits are safer when persisted on meaningful changes, with debouncing to control write frequency, plus a visibility-loss checkpoint. The exact storage layer depends on the application: a small preference may fit in <code>localStorage</code>; structured offline data generally belongs in IndexedDB; authoritative records should be confirmed by the server.</p>
<p>This is less dramatic than a final save callback, and that is the point. Durability works better as an ongoing property than as an emergency action.</p>
<p>Session termination needs the same rethink. A server should not rely on a browser page to announce that a person has “left.” Tabs crash, networks vanish and devices sleep. Expiry, revocable credentials, server-side session state and, where appropriate, heartbeats are better foundations than an exit beacon.</p>
<h3>When <code>beforeunload</code> is still appropriate</h3>
<p><code>beforeunload</code> is not a drop-in replacement for <code>unload</code>. Its legitimate use is much narrower: asking the browser to warn a user who is about to abandon unsaved work.</p>
<p>Modern browsers require prior user interaction before showing the dialog, display browser-controlled generic text, and may not fire the event on mobile when the browser is killed from the background. Firefox also excludes pages with <code>beforeunload</code> listeners from bfcache. The listener should therefore exist only while the page is genuinely dirty, and be removed as soon as the work is safe.<a class="mt-citation" href="#source-12" aria-label="Source 12">[12]</a></p>
<pre class="mt-code" aria-label="Conditional beforeunload example"><code>function warnAboutUnsavedChanges(event) {
  event.preventDefault();
  event.returnValue = '';
}

function setDirty(isDirty) {
  const method = isDirty ? 'addEventListener' : 'removeEventListener';
  window[method]('beforeunload', warnAboutUnsavedChanges);
}</code></pre>
<p>The dialog is a user-facing safety net, not a persistence mechanism. The application should already be saving what it can.</p>
</section>
<section id="resource-cleanup" class="mt-article__section">
<h2>Resource cleanup without a final callback</h2>
<p>“Close everything in <code>unload</code>” sounds disciplined, but it combines resources with very different lifetimes. The better question is what must happen while the page is still observable, what must be reversible after restoration, and what the browser can reclaim by destroying the process.</p>
<dl class="mt-definition-list">
<div>
<dt>Durable application data</dt>
<dd>Commit it as part of the user action or shortly afterward. An IndexedDB transaction should not depend on a final page event to become meaningful.</dd>
</p></div>
<div>
<dt>User-sensitive capture</dt>
<dd>Camera, microphone and screen-capture tracks should follow explicit product and visibility rules. Stop them when the user should reasonably expect capture to end, not merely because a document might be destroyed.</dd>
</p></div>
<div>
<dt>Long-lived connections</dt>
<dd>WebSockets and WebRTC sessions may need to pause, close or reconnect depending on the product. Any action taken on <code>pagehide</code> must account for the possibility of a later <code>pageshow</code>.</dd>
</p></div>
<div>
<dt>Cross-context coordination</dt>
<dd>Web Locks, BroadcastChannel messages and worker coordination should tolerate a participant disappearing without a farewell message. Reacquire or resynchronize when the page becomes active again.</dd>
</p></div>
<div>
<dt>Timers, observers and background work</dt>
<dd>Assume they can be throttled, frozen or terminated. Pause work that has no value while hidden, but do not treat a timer callback as evidence that the page will remain alive.</dd>
</p></div>
</dl>
<p>Bfcache eligibility rules for particular APIs also evolve as engines learn to suspend them safely. A static list copied into application architecture will age badly. Test the actual page, inspect current browser diagnostics, and keep correctness independent from whether the browser chooses to cache, freeze or destroy it.</p>
</section>
<section id="browser-differences" class="mt-article__section">
<h2>Browser differences still matter</h2>
<p>The direction of travel is shared, but the details remain engine-specific.</p>
<h3>WebKit and Safari</h3>
<p>WebKit has unusually long historical memory here. Its engineers were documenting the conflict between unload handlers and the Page Cache in 2009, and the cache itself predates Safari’s first public beta. WebKit’s solution was to make navigation suspendable and to use <code>pagehide</code>/<code>pageshow</code> for state that can be left and restored.<a class="mt-citation" href="#source-4" aria-label="Source 4">[4]</a><a class="mt-citation" href="#source-5" aria-label="Source 5">[5]</a></p>
<p>Current compatibility guidance describes Safari as prioritizing its page cache over firing <code>unload</code> in relevant cases. That is good for history-navigation performance and another reason not to treat observed desktop behavior in one engine as a cross-browser guarantee.<a class="mt-citation" href="#source-19" aria-label="Source 19">[19]</a></p>
<h3>Gecko and Firefox</h3>
<p>Firefox’s bfcache lineage is also early. Mozilla’s Firefox 1.5 documentation explains that entire pages, including JavaScript state, could be kept in memory, and introduced <code>pageshow</code>/<code>pagehide</code> as the way to cooperate with that model.<a class="mt-citation" href="#source-6" aria-label="Source 6">[6]</a></p>
<p>On current Firefox, unload listeners can still prevent bfcache use, and MDN documents the same issue for <code>beforeunload</code>. That makes conditional listener registration particularly important.<a class="mt-citation" href="#source-12" aria-label="Source 12">[12]</a><a class="mt-citation" href="#source-19" aria-label="Source 19">[19]</a></p>
<h3>Chromium and Chrome</h3>
<p>Chromium came later to broad bfcache deployment and spent years on compatibility work and outreach. Chrome’s unload deprecation now flips the old desktop tradeoff: instead of sacrificing bfcache to preserve a callback that was already unreliable on mobile, Chrome is progressively making the callback disabled by default.<a class="mt-citation" href="#source-1" aria-label="Source 1">[1]</a></p>
<p>Chrome also has the richest current diagnostics, including a DevTools bfcache test and <code>PerformanceNavigationTiming.notRestoredReasons</code>. That API shipped in Chrome 123 and can expose blocking reasons such as an unload listener, including reasons found in same-origin frames. It should be treated as a Chromium diagnostic, not assumed to exist everywhere.<a class="mt-citation" href="#source-14" aria-label="Source 14">[14]</a></p>
<p>The common baseline is still useful: Page Visibility and the page-transition events are broadly implemented. The edges—what blocks bfcache, which resources can be frozen, whether an exit event fires, and which diagnostics are exposed—must be tested per engine and device class.</p>
</section>
<section id="chrome-rollout" class="mt-article__section">
<h2>What Chrome is changing in 2026</h2>
<p>Chrome is not simply deleting the <code>unload</code> property in one release. It is changing the default behavior in stages so that registered handlers stop firing unless the page has opted back in.</p>
<figure class="mt-figure mt-figure--wide">
      <img decoding="async" class="mt-figure__image" src="/wp-content/themes/mobiletech-heritage/assets/page-lifecycle/chrome-unload-rollout-2026.svg" width="1280" height="520" loading="lazy" alt="Chrome unload deprecation schedule from one percent of page loads in March 2026 to a planned one hundred percent in September 2026."><figcaption>Chrome’s published all-origin schedule as of August 29, 2026. Percentages are shares of Chrome page loads; dates and milestones may change.<a class="mt-citation" href="#source-1" aria-label="Source 1">[1]</a></figcaption></figure>
<p>The rollout follows earlier work on the top 50 sites during 2025. For all other origins, Chrome’s published 2026 stages are:</p>
<ul class="mt-rollout-list">
<li><time datetime="2026-03-10">March 10</time>: 1%</li>
<li><time datetime="2026-04-07">April 7</time>: 5%</li>
<li><time datetime="2026-05-05">May 5</time>: 10%</li>
<li><time datetime="2026-06-02">June 2</time>: 20%</li>
<li><time datetime="2026-06-30">June 30</time>: 40%</li>
<li><time datetime="2026-07-28">July 28</time>: 60%</li>
<li><time datetime="2026-08-25">August 25</time>: 80%</li>
<li><time datetime="2026-09-22">September 22</time>: planned 100%</li>
</ul>
<p>Sites can currently control the transition with the <code>unload</code> Permissions Policy. A site that has removed its dependency can disable the event deliberately with:</p>
<pre class="mt-code" aria-label="Disable unload with Permissions Policy"><code>Permissions-Policy: unload=()</code></pre>
<p>A top-level document that needs temporary compatibility can opt back in with:</p>
<pre class="mt-code" aria-label="Temporarily enable unload with Permissions Policy"><code>Permissions-Policy: unload=self</code></pre>
<p>Cross-origin frames need compatible policy declarations through the ancestor chain and appropriate iframe permissions. Chrome also provides enterprise policy and local testing flags. These are migration controls, not a new reliability guarantee: opting in does not make the event dependable on mobile.<a class="mt-citation" href="#source-20" aria-label="Source 20">[20]</a></p>
<p>Chrome says its long-term aim is to remove <code>unload</code>, but the published schedule covers staged changes to whether handlers fire by default. It does not schedule physical removal of the API.<a class="mt-citation" href="#source-2" aria-label="Source 2">[2]</a></p>
</section>
<section id="audit-migration" class="mt-article__section">
<h2>How to audit an unload dependency</h2>
<p>Start by finding the dependency, including code you did not write. Search first-party source, compiled bundles, tag-manager output and third-party frames for <code>unload</code>, <code>onunload</code> and unconditional <code>beforeunload</code> registration.</p>
<p>Then test behavior rather than stopping at source search:</p>
<ol>
<li>Use Chrome DevTools’ Application → Back/forward cache test.</li>
<li>Navigate away and back, then inspect <code>pageshow</code> and <code>event.persisted</code>.</li>
<li>Collect field diagnostics with <code>notRestoredReasons</code> where Chromium support is acceptable.</li>
<li>Use a report-only unload Permissions Policy to discover attempted registrations before enforcing the policy.</li>
<li>Repeat the test in Safari and Firefox, on desktop and mobile hardware.</li>
</ol>
<figure class="mt-figure mt-figure--wide">
      <img decoding="async" class="mt-figure__image" src="/wp-content/themes/mobiletech-heritage/assets/page-lifecycle/unload-migration-map.svg" width="1280" height="820" loading="lazy" alt="Decision map matching former unload use cases to analytics delivery, state persistence, pagehide, conditional beforeunload and reversible resource handling."><figcaption>There is no universal replacement event. Migrate according to the job the old handler was trying to perform.</figcaption></figure>
<div class="mt-migration-cards">
<section class="mt-migration-card">
<h3>Analytics</h3>
<p>Batch during the session and make a best-effort Beacon or Fetch keepalive flush when visibility becomes hidden. Do not promise delivery that the transport cannot guarantee.</p>
</section>
<section class="mt-migration-card">
<h3>State saving</h3>
<p>Persist on meaningful changes, debounce routine writes, and checkpoint on visibility loss. Treat server acknowledgement as the authority for critical records.</p>
</section>
<section class="mt-migration-card">
<h3>Navigation detection</h3>
<p>Use <code>pagehide</code>. If the page changes resources or pauses work, add a corresponding <code>pageshow</code> restoration path.</p>
</section>
<section class="mt-migration-card">
<h3>Unsaved-change warning</h3>
<p>Register <code>beforeunload</code> only while unsaved user work actually exists, and remove it immediately after saving.</p>
</section>
<section class="mt-migration-card">
<h3>Resource cleanup</h3>
<p>Decide whether the resource needs early release, reversible suspension or no script cleanup at all. Test current bfcache behavior instead of relying on old blocker lists.</p>
</section>
<section class="mt-migration-card">
<h3>Session termination</h3>
<p>Move authority to the server: expiry, revocation, acknowledgement and resilient presence logic. A disappearing tab cannot be a trusted logout signal.</p>
</section></div>
<p><a href="/mobile-web-performance-reality-check-2026-public-benchmarks-and-a-site-homepage-audit/">MobileTech’s 2026 homepage audit</a> examined startup weight, JavaScript and third-party pressure. Bfcache affects a different phase—return navigation—but reinforces the same architectural point: application-like pages need explicit lifecycle design as well as a fast initial load.<a class="mt-citation" href="#source-21" aria-label="Source 21">[21]</a></p>
</section>
<section class="mt-article__section mt-article__section--conclusion">
<h2>From document teardown to application resilience</h2>
<p>The deprecation of <code>unload</code> is easy to frame as browser housekeeping. That misses the larger change.</p>
<p>The early web inherited a document lifecycle: load the page, use it, tear it down. The modern web increasingly behaves like an application platform running inside another application, under an operating system that may pause, cache, discard or kill it. Navigation itself may preserve the document rather than destroy it.</p>
<p>So the platform’s advice has moved earlier. Save state while the page is still visible or when it first becomes hidden. Send analytics without blocking the next action. Treat navigation as potentially reversible. Restore time-sensitive state when a cached page returns. Use <code>beforeunload</code> only when the user genuinely needs a warning.</p>
<p>The page may receive another event. It may come back exactly where it was. Or it may disappear without one last line of JavaScript.</p>
<p>That is not a lifecycle edge case anymore. It is the lifecycle.</p>
</section>
<footer class="mt-sources" aria-labelledby="sources-title">
<h2 id="sources-title">Sources and further reading</h2>
<p class="mt-sources__note">Schedule and compatibility information is current as of August 29, 2026. Browser-specific behavior follows the cited vendor documentation.</p>
<ol class="mt-sources__list">
<li id="source-1"><a href="https://developer.chrome.com/docs/web-platform/deprecating-unload">Chrome for Developers, “Deprecating the unload event”</a>. Published August 10, 2023; updated July 14, 2026.</li>
<li id="source-2"><a href="https://developer.chrome.com/docs/web-platform/chrome-deprecation">Chrome for Developers, “Feature deprecation and removal in Chrome”</a>. Updated August 21, 2026.</li>
<li id="source-3"><a href="https://html.spec.whatwg.org/multipage/nav-history-apis.html#the-pagetransitionevent-interface">WHATWG HTML Living Standard, <code>PageTransitionEvent</code></a>. Living standard; accessed August 29, 2026.</li>
<li id="source-4"><a href="https://webkit.org/blog/427/webkit-page-cache-i-the-basics/">WebKit, “Page Cache I — The Basics”</a>. September 16, 2009.</li>
<li id="source-5"><a href="https://webkit.org/blog/516/webkit-page-cache-ii-the-unload-event/">WebKit, “Page Cache II — The unload Event”</a>. September 21, 2009.</li>
<li id="source-6"><a href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/1.5/Using_Firefox_1.5_caching">MDN, “Using Firefox 1.5 caching”</a>. Historical Mozilla documentation; updated October 31, 2025.</li>
<li id="source-7"><a href="https://html.spec.whatwg.org/multipage/interaction.html#page-visibility">WHATWG HTML Living Standard, Page Visibility</a>. Living standard; accessed August 29, 2026.</li>
<li id="source-8"><a href="https://html.spec.whatwg.org/multipage/browsing-the-web.html#unloading-documents">WHATWG HTML Living Standard, unloading documents</a>. Living standard; accessed August 29, 2026.</li>
<li id="source-9"><a href="https://developer.chrome.com/docs/web-platform/page-lifecycle-api">Chrome for Developers, “Page Lifecycle API”</a>. Chromium-oriented lifecycle guidance.</li>
<li id="source-10"><a href="https://wicg.github.io/page-lifecycle/">WICG Page Lifecycle</a>. Community Group draft; not a W3C standards-track Recommendation.</li>
<li id="source-11"><a href="https://www.w3.org/TR/beacon/">W3C, Beacon</a>. W3C Candidate Recommendation, August 2022.</li>
<li id="source-12"><a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event">MDN, <code>beforeunload</code> event</a>. Compatibility and usage guidance; accessed August 29, 2026.</li>
<li id="source-13"><a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/pagehide_event">MDN, <code>pagehide</code> event</a>. Compatibility and mobile reliability guidance; accessed August 29, 2026.</li>
<li id="source-14"><a href="https://developer.chrome.com/docs/web-platform/bfcache-notrestoredreasons">Chrome for Developers, “Back/forward cache notRestoredReasons API”</a>. Updated March 26, 2024.</li>
<li id="source-15"><a href="https://web.dev/articles/bfcache">web.dev, “Back/forward cache”</a>. Mechanism, compatibility and testing guidance.</li>
<li id="source-16"><a href="https://developer.android.com/guide/components/activities/process-lifecycle">Android Developers, Processes and app lifecycle</a>. Official Android process-management guidance.</li>
<li id="source-17"><a href="https://developer.apple.com/library/archive/documentation/Performance/Conceptual/EnergyGuide-iOS/WorkLessInTheBackground.html">Apple Developer Documentation Archive, “Work Less in the Background”</a>. iOS suspension and background-resource guidance.</li>
<li id="source-18"><a href="https://fetch.spec.whatwg.org/#request-keepalive-flag">WHATWG Fetch Standard, request keepalive flag</a>. Living standard; accessed August 29, 2026.</li>
<li id="source-19"><a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/unload_event">MDN, <code>unload</code> event</a>. Cross-browser compatibility guidance; accessed August 29, 2026.</li>
<li id="source-20"><a href="https://github.com/fergald/docs/blob/master/explainers/permissions-policy-deprecate-unload.md">Permissions Policy unload deprecation explainer</a>. Chromium proposal and policy examples.</li>
<li id="source-21"><a href="https://www.mobiletech.mobi/mobile-web-performance-reality-check-2026-public-benchmarks-and-a-site-homepage-audit/">MobileTech, “Mobile Web Performance Reality Check 2026”</a>. Published June 9, 2026.</li>
</ol>
</footer>
</div>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Mobile Web Performance Reality Check 2026: Public Benchmarks and a 39-Site Homepage Audit</title>
		<link>https://www.mobiletech.mobi/mobile-web-performance-reality-check-2026-public-benchmarks-and-a-site-homepage-audit/</link>
		
		<dc:creator><![CDATA[mobiletech_admin]]></dc:creator>
		<pubDate>Tue, 09 Jun 2026 00:21:32 +0000</pubDate>
				<category><![CDATA[Mobile SEO]]></category>
		<category><![CDATA[CLS]]></category>
		<category><![CDATA[Core Web Vitals]]></category>
		<category><![CDATA[CrUX]]></category>
		<category><![CDATA[ecommerce performance]]></category>
		<category><![CDATA[HTTP Archive]]></category>
		<category><![CDATA[images]]></category>
		<category><![CDATA[INP]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[LCP]]></category>
		<category><![CDATA[Lighthouse]]></category>
		<category><![CDATA[mobile performance]]></category>
		<category><![CDATA[PageSpeed Insights]]></category>
		<category><![CDATA[third-party scripts]]></category>
		<category><![CDATA[Web Almanac]]></category>
		<category><![CDATA[web performance]]></category>
		<guid isPermaLink="false">https://www.mobiletech.mobi/?p=426</guid>

					<description><![CDATA[Mobile web performance in 2026 is better understood as a pattern problem than a mystery. Public benchmark data from HTTP Archive, CrUX, and Google documentation shows the same recurring constraints year after year: LCP is still image-led, pages are still heavy, JavaScript still dominates request count, and third parties still crowd the critical path. A focused 39-site homepage audit illustrates how those benchmark patterns appear across ecommerce, news, SaaS, travel, finance, government, and publishing.]]></description>
										<content:encoded><![CDATA[<p><strong>Mobile web performance in 2026 is better, but not under control.</strong> That is the clearest conclusion from both public benchmark data and a focused audit of 39 real mobile homepages. Core Web Vitals have improved, especially INP and CLS, yet the same structural problems continue to dominate: delayed LCP, oversized JavaScript, unstable layouts, heavy media surfaces, and long chains of third-party or external-domain requests.</p>
<p>The important point is not that teams lack guidance. The web has had years of documentation, tooling, and metric thresholds. The harder truth is that many homepages are now expected to do too many things at once: market, personalize, track, experiment, collect consent, recommend content, and render rich interfaces before the primary content has even settled. In that environment, performance failure is rarely one bad image or one careless script. It is usually the cumulative result of too many legitimate business demands competing for the same startup budget.</p>
<p>Public data supports that reading. In the <a href="https://almanac.httparchive.org/en/2025/performance" target="_blank" rel="noopener">Web Almanac 2025 Performance chapter</a>, <strong>48% of mobile websites</strong> achieved good Core Web Vitals, up from 44% in 2024 and 36% in 2023. In the <a href="https://developer.chrome.com/docs/crux/release-notes" target="_blank" rel="noopener">Chrome UX Report April 2026 release</a>, <strong>56.4% of origins</strong> had good Core Web Vitals overall, with <strong>68.9% good LCP</strong>, <strong>81.3% good CLS</strong>, and <strong>87.1% good INP</strong>. That is real progress. It is also a reminder that roughly half of origins still fail the full assessment.</p>
<p>The weakest link remains familiar. Web Almanac 2025 reports that only <strong>62% of phone experiences</strong> had good LCP, versus <strong>77% good INP</strong> and <strong>81% good CLS</strong>. Images remain the dominant LCP content type: <strong>76% of mobile pages</strong> have an image as the LCP element. At the same time, page weight continues to rise. The <a href="https://almanac.httparchive.org/en/2025/page-weight" target="_blank" rel="noopener">Web Almanac 2025 Page Weight chapter</a> reports a median mobile homepage of <strong>2.6 MB</strong>, including <strong>632 KB JavaScript</strong>, <strong>911 KB images</strong>, <strong>122 KB fonts</strong>, and <strong>77 KB CSS</strong>. The <a href="https://almanac.httparchive.org/en/2025/third-parties" target="_blank" rel="noopener">Third Parties chapter</a> adds another structural constraint: more than nine in ten pages use at least one third party, and the median mobile page made <strong>79 third-party requests</strong> across all sites, rising to <strong>106</strong> among the top 1,000 sites.</p>
<p>To test whether those broad signals still show up in concrete mobile homepages, this article also looks at a focused audit of <strong>39 public homepages</strong> across ecommerce, news/media, SaaS, travel, finance, government, and blogs/publishing. This audit is <strong>illustrative, not representative</strong>. It should not be read as a benchmark for the web. Its value is narrower: it shows how recurring public-data patterns behave when you inspect recognizable, high-complexity pages one by one.</p>
<h2 id="chart-snapshot">Public benchmark vs focused audit: the gap at a glance</h2>
<p>Public web metrics show broad improvement, but the focused 39-site audit paints a harsher picture for complex homepages. The difference does not prove contradiction. It shows how much weaker performance can look when the sample is limited to visible, high-complexity entry pages.</p>
<figure class="chart">
  <svg viewBox="0 0 900 420" role="img" aria-label="Benchmark versus focused audit comparison" aria-describedby="snapshot-desc">
    <desc id="snapshot-desc">Bar chart comparing public benchmark figures with focused audit results for good Core Web Vitals pass rate, LCP weakness, and homepage complexity signals.</desc>

    <style>
      .bg { fill: transparent; }
      .axis { stroke: var(--faint, #9aa6b2); stroke-width: 1.2; }
      .grid { stroke: var(--line, #e4e8ee); stroke-width: 1; }
      .label { fill: var(--ink-2, #24303f); font-family: "IBM Plex Sans", system-ui, sans-serif; font-size: 14px; }
      .small { fill: var(--muted, #697585); font-family: "IBM Plex Mono", ui-monospace, monospace; font-size: 12px; }
      .value { fill: var(--ink, #0e1726); font-family: "IBM Plex Mono", ui-monospace, monospace; font-size: 13px; }
      .bar-public { fill: var(--navy, #0d1b2e); }
      .bar-audit { fill: var(--orange, #ec5a1c); }
      .legend-text { fill: var(--ink-soft, #44525f); font-family: "IBM Plex Mono", ui-monospace, monospace; font-size: 13px; }
    </style>

    <rect class="bg" x="0" y="0" width="900" height="420" />

    <rect x="540" y="26" width="14" height="14" rx="2" class="bar-public" />
    <text class="legend-text" x="562" y="38">Public benchmark</text>
    <rect x="690" y="26" width="14" height="14" rx="2" class="bar-audit" />
    <text class="legend-text" x="712" y="38">Focused 39-site audit</text>

    <line class="axis" x1="220" y1="70" x2="220" y2="360" />
    <line class="axis" x1="220" y1="360" x2="830" y2="360" />

    <line class="grid" x1="220" y1="120" x2="830" y2="120" />
    <line class="grid" x1="220" y1="200" x2="830" y2="200" />
    <line class="grid" x1="220" y1="280" x2="830" y2="280" />

    <text class="small" x="220" y="378">0%</text>
    <text class="small" x="360" y="378">20%</text>
    <text class="small" x="500" y="378">40%</text>
    <text class="small" x="640" y="378">60%</text>
    <text class="small" x="780" y="378">80%</text>

    <text class="label" x="28" y="110">Good CWV pass rate</text>
    <rect class="bar-public" x="220" y="88" width="395" height="22" rx="4" />
    <text class="value" x="625" y="104">56.4% origins in CrUX</text>
    <rect class="bar-audit" x="220" y="116" width="193" height="22" rx="4" />
    <text class="value" x="423" y="132">28.2% pages in audit</text>

    <text class="label" x="28" y="190">Pages with poor LCP</text>
    <rect class="bar-public" x="220" y="168" width="266" height="22" rx="4" />
    <text class="value" x="496" y="184">38% mobile experiences not good</text>
    <rect class="bar-audit" x="220" y="196" width="414" height="22" rx="4" />
    <text class="value" x="644" y="212">59.0% pages above 2.5s</text>

    <text class="label" x="28" y="270">Pages with image LCP</text>
    <rect class="bar-public" x="220" y="248" width="532" height="22" rx="4" />
    <text class="value" x="762" y="264">76% mobile pages</text>
    <rect class="bar-audit" x="220" y="276" width="414" height="22" rx="4" />
    <text class="value" x="644" y="292">59.0% audit pages</text>

    <text class="small" x="220" y="404">Sources: Web Almanac 2025, CrUX April 2026, and the focused 39-site audit.</text>
  </svg>
  <figcaption>Benchmark data compared with the focused 39-site homepage audit.</figcaption>
</figure>
<h2>Key findings from the focused audit</h2>
<ul>
<li><strong>11/39</strong> pages passed all three field Core Web Vitals thresholds.</li>
<li><strong>23/39</strong> had field LCP above 2.5 seconds.</li>
<li><strong>19/39</strong> had field INP above 200 ms.</li>
<li><strong>16/39</strong> had field CLS above 0.1.</li>
<li><strong>25/39</strong> transferred more than 1 MB of JavaScript in the lab run.</li>
<li><strong>20/39</strong> transferred more than 3 MB total.</li>
<li><strong>27/39</strong> made more than 50 external-domain requests.</li>
<li><strong>23/39</strong> had an image-classified LCP element.</li>
<li>Government pages were the lightest vertical in the sample; SaaS and travel were the weakest on field Core Web Vitals.</li>
</ul>
<p>The headline is not that the modern web is careless. It is that startup complexity has become normal. Too many mobile homepages now behave like operating environments rather than documents or task-first entry points, and that shift has a predictable cost.</p>
<h2>Methodology</h2>
<p>This article combines two evidence layers.</p>
<p>The first is <strong>public benchmark data</strong> from primary sources:</p>
<table>
<thead>
<tr>
<th>Source</th>
<th>What it contributes</th>
</tr>
</thead>
<tbody>
<tr>
<td>HTTP Archive / Web Almanac 2025 Performance</td>
<td>Core Web Vitals pass rates, LCP/INP/CLS patterns, LCP content types, prioritization practices</td>
</tr>
<tr>
<td>HTTP Archive / Web Almanac 2025 Page Weight</td>
<td>Median mobile page weight and resource composition</td>
</tr>
<tr>
<td>HTTP Archive / Web Almanac 2025 Third Parties</td>
<td>Third-party prevalence, request counts, request categories</td>
</tr>
<tr>
<td>HTTP Archive / Web Almanac 2025 Ecommerce</td>
<td>Ecommerce platform-level Core Web Vitals differences</td>
</tr>
<tr>
<td>Chrome UX Report release notes and documentation</td>
<td>Current origin-level field status for Core Web Vitals</td>
</tr>
<tr>
<td>Google / web.dev Core Web Vitals documentation</td>
<td>Metric definitions, thresholds, and optimization guidance</td>
</tr>
<tr>
<td>PageSpeed Insights / Lighthouse documentation</td>
<td>How to read lab vs field data and what diagnostics can and cannot prove</td>
</tr>
</tbody>
</table>
<p>The second is a <strong>focused 39-site audit</strong>. The candidate list covered seven verticals: ecommerce, news/media, SaaS, travel, finance, government, and blogs/publishing. Only pages with both usable CrUX field data and a completed mobile Lighthouse run were included. Pages whose lab runs did not complete reliably were excluded rather than padded with placeholders.</p>
<ul>
<li>Field metrics are from CrUX: LCP, INP, CLS, and TTFB where available.</li>
<li>Lab diagnostics come from a single mobile Lighthouse run per page: Performance score, TBT, transfer size, request counts, resource breakdown, and external-domain request counts.</li>
<li>INP is treated as a field metric only. TBT is used as a lab signal for main-thread blocking, not as a substitute for INP.</li>
<li>External-domain requests are classified by registrable-domain mismatch. That includes independent third parties, but it can also include owned CDNs and adjacent first-party infrastructure.</li>
<li>The audit is homepage-only, small, and non-representative.</li>
</ul>
<p>Core Web Vitals thresholds follow current Google guidance: good LCP is <strong>2.5 seconds or less</strong>, good INP is <strong>200 ms or less</strong>, and good CLS is <strong>0.1 or less</strong>, evaluated at the 75th percentile and segmented by device class.</p>
<h2>Public benchmark reality check</h2>
<h3>Core Web Vitals improved, but mobile still lags where it matters most</h3>
<p>The broad trend is positive. Web Almanac 2025 shows mobile good-CWV rates rising from <strong>36% in 2023</strong> to <strong>44% in 2024</strong> and <strong>48% in 2025</strong>. Desktop reached 56% in 2025. The same chapter also shows that homepages remain harder than secondary pages: <strong>45% of mobile homepages</strong> had good CWV, compared with <strong>56% of mobile secondary pages</strong>.</p>
<p>CrUX’s April 2026 release looks somewhat stronger at the origin level, with <strong>56.4% of origins</strong> passing good Core Web Vitals overall. These figures are not directly equivalent to homepage-level cuts in Web Almanac, but the direction is the same: the web is improving, yet homepages remain the place where product ambition most often overruns performance discipline.</p>
<h3>LCP is still the metric that exposes architectural shortcuts</h3>
<p>LCP remains the most stubborn mobile weakness. Web Almanac 2025 reports <strong>62% good LCP on phones</strong>, compared with <strong>77% good INP</strong> and <strong>81% good CLS</strong>. It is tempting to translate that into “optimize your images,” but that is too shallow. LCP is usually a critical-path story: server response, HTML delivery, resource discovery, fetch priority, render-blocking CSS, script competition, font behavior, and whether the likely LCP element is visible to the browser early enough to matter.</p>
<p>Google’s own LCP guidance points directly at delayed discovery: dynamically inserted images, CSS background images, hidden lazy-loading patterns, and resources invisible to the preload scanner. The public-data gap is not abstract. In Web Almanac 2025, only <strong>17.3% of mobile pages with LCP images</strong> used <code>fetchpriority="high"</code>, only about <strong>2.1%</strong> used preload for LCP prioritization, and roughly <strong>17%</strong> still lazy-loaded the LCP image.</p>
<p>That is why LCP remains such a useful reality check. It punishes not only large assets, but also organizational indecision about what deserves priority.</p>
<h3>INP looks healthier, but JavaScript is still where mobile debt accumulates</h3>
<p>Public data makes INP look comparatively strong. CrUX April 2026 reports <strong>87.1% of origins</strong> with good INP, and Web Almanac 2025 reports <strong>77% good INP on phones</strong>. But those numbers should not be read as proof that mobile responsiveness is solved.</p>
<p>Web Almanac notes that while mobile INP improved, <strong>TBT increased 58%</strong>. That is an uncomfortable but useful signal: pages may be performing better on measured user interactions while still carrying increasingly expensive JavaScript startup behavior. Google’s INP guidance explains the risk clearly. A page can look ready before it is ready to respond. Long tasks from parsing, compiling, and executing script during startup still create latent interaction cost.</p>
<p>For product teams, that distinction matters. INP measures real user outcomes. TBT is a lab diagnostic. They are not interchangeable. But large JS payloads, hydration-heavy shells, early tag managers, and broad script ownership almost always mean the same thing operationally: the margin for interaction quality is thinner than it appears.</p>
<h3>CLS improved, but regressions still come from boring mistakes</h3>
<p>CLS is the success story of recent years, with <strong>81% good CLS on mobile</strong> in Web Almanac 2025. But the remaining failures are not mysterious. The same chapter reports that <strong>62% of mobile pages</strong> still fail to set dimensions on at least one image. Google continues to emphasize basic layout reservation: explicit media dimensions, <code>aspect-ratio</code>, and reserved space for ads, embeds, banners, and other late-loading surfaces.</p>
<p>Consent interfaces remain especially revealing because they cut across product, legal, and analytics priorities. Google’s cookie-notice guidance notes that these notices often load early, affect LCP, frequently trigger CLS, and can worsen INP when acceptance fires a burst of third-party activity. In other words, one small UI block often exposes the full governance problem in miniature.</p>
<h3>Page weight and third parties are not edge cases. They are the default.</h3>
<p>The median mobile homepage in Web Almanac 2025 is <strong>2.6 MB</strong>, up 8.4% year over year. Images remain the largest resource class, but JavaScript is not far behind: <strong>911 KB images</strong> and <strong>632 KB JavaScript</strong> at the median.</p>
<p>Third parties are even more structurally embedded. More than nine in ten pages include them, and the median mobile page makes <strong>79 third-party requests</strong> across all sites. Among the top 1,000 sites, that rises to <strong>106</strong>. Google’s third-party JavaScript guidance is blunt for good reason: third-party code consumes bandwidth, extends request chains, and adds parse, compile, and execution work that the core team does not fully control. At that point, the request chain is no longer incidental plumbing. It is part of the product.</p>
<h2>Focused 39-site mobile audit</h2>
<h3>What this small audit is useful for</h3>
<p>This audit is intentionally narrow. It is not a ranking of the web, and it should not be used to generalize from one homepage to an entire vertical. Its value is simpler: it tests whether the broad warnings from public benchmark data still appear when you inspect real mobile homepages that many teams would recognize as mature, high-traffic surfaces.</p>
<p>In most cases, they do.</p>
<h3>Audit summary by vertical</h3>
<table>
<thead>
<tr>
<th>Vertical</th>
<th>Sites</th>
<th>Median LH score</th>
<th>Median LCP ms</th>
<th>Median INP ms</th>
<th>Median CLS</th>
<th>Median TBT ms</th>
<th>Median total MB</th>
<th>Median JS MB</th>
<th>Median image MB</th>
<th>Median requests</th>
<th>Median ext-domain reqs</th>
</tr>
</thead>
<tbody>
<tr>
<td>Ecommerce</td>
<td>5</td>
<td>33</td>
<td>2681</td>
<td>279</td>
<td>0.14</td>
<td>1318</td>
<td>3.47</td>
<td>2.27</td>
<td>0.37</td>
<td>229</td>
<td>189</td>
</tr>
<tr>
<td>News / media</td>
<td>5</td>
<td>40</td>
<td>1297</td>
<td>131</td>
<td>0.01</td>
<td>739</td>
<td>6.69</td>
<td>3.66</td>
<td>1.53</td>
<td>599</td>
<td>494</td>
</tr>
<tr>
<td>SaaS</td>
<td>6</td>
<td>38</td>
<td>3108</td>
<td>292</td>
<td>0.14</td>
<td>1033</td>
<td>5.90</td>
<td>2.22</td>
<td>1.18</td>
<td>250</td>
<td>148</td>
</tr>
<tr>
<td>Travel</td>
<td>5</td>
<td>41</td>
<td>2813</td>
<td>338</td>
<td>0.17</td>
<td>663</td>
<td>3.56</td>
<td>2.24</td>
<td>0.78</td>
<td>184</td>
<td>159</td>
</tr>
<tr>
<td>Finance</td>
<td>6</td>
<td>35</td>
<td>2778</td>
<td>210</td>
<td>0.11</td>
<td>1395</td>
<td>3.30</td>
<td>2.59</td>
<td>0.13</td>
<td>162</td>
<td>107</td>
</tr>
<tr>
<td>Government</td>
<td>6</td>
<td>62</td>
<td>1278</td>
<td>120</td>
<td>0.00</td>
<td>58</td>
<td>1.10</td>
<td>0.49</td>
<td>0.20</td>
<td>46</td>
<td>8</td>
</tr>
<tr>
<td>Blogs / publishing</td>
<td>6</td>
<td>74</td>
<td>2568</td>
<td>130</td>
<td>0.00</td>
<td>37</td>
<td>1.52</td>
<td>0.67</td>
<td>0.23</td>
<td>68</td>
<td>32</td>
</tr>
</tbody>
</table>
<h3 id="chart-lcp-by-vertical">Median mobile LCP by vertical in the 39-site audit</h3>
<p>This small-sample comparison is not representative of the web, but it makes one pattern obvious: task-first and simpler homepage models still retain a measurable performance advantage.</p>
<figure class="chart">
<svg viewBox="0 0 860 420" role="img" aria-label="Median LCP by vertical" aria-describedby="chart-lcp-desc">
  <desc id="chart-lcp-desc">Horizontal bar chart comparing median LCP in milliseconds across seven verticals in the 39-site audit.</desc>

  <style>
    .chart-bg { fill: transparent; }
    .chart-grid { stroke: var(--line, #e4e8ee); stroke-width: 1; }
    .chart-axis { stroke: var(--faint, #9aa6b2); stroke-width: 1.2; }
    .chart-label { fill: var(--ink-2, #24303f); font-family: "IBM Plex Sans", system-ui, sans-serif; font-size: 14px; }
    .chart-value { fill: var(--ink, #0e1726); font-family: "IBM Plex Mono", ui-monospace, monospace; font-size: 13px; }
    .chart-note { fill: var(--muted, #697585); font-family: "IBM Plex Mono", ui-monospace, monospace; font-size: 12px; }
    .bar { fill: var(--orange, #ec5a1c); }
    .bar-good { fill: var(--navy, #0d1b2e); }
    .threshold { stroke: var(--ink-2, #24303f); stroke-width: 2; stroke-dasharray: 6 5; }
  </style>

  <rect class="chart-bg" x="0" y="0" width="860" height="420" />

  <line class="chart-grid" x1="220" y1="70" x2="800" y2="70" />
  <line class="chart-grid" x1="220" y1="120" x2="800" y2="120" />
  <line class="chart-grid" x1="220" y1="170" x2="800" y2="170" />
  <line class="chart-grid" x1="220" y1="220" x2="800" y2="220" />
  <line class="chart-grid" x1="220" y1="270" x2="800" y2="270" />
  <line class="chart-grid" x1="220" y1="320" x2="800" y2="320" />
  <line class="chart-grid" x1="220" y1="370" x2="800" y2="370" />

  <line class="chart-axis" x1="220" y1="40" x2="220" y2="380" />
  <line class="chart-axis" x1="220" y1="380" x2="800" y2="380" />

  <!-- 2.5s threshold -->
  <line class="threshold" x1="635" y1="40" x2="635" y2="380" />
  <text class="chart-note" x="642" y="55">2.5s good LCP threshold</text>

  <text class="chart-label" x="40" y="75">News / media</text>
  <rect class="bar-good" x="220" y="58" width="215" height="24" rx="4" />
  <text class="chart-value" x="445" y="75">1297 ms</text>

  <text class="chart-label" x="40" y="125">Government</text>
  <rect class="bar-good" x="220" y="108" width="212" height="24" rx="4" />
  <text class="chart-value" x="442" y="125">1278 ms</text>

  <text class="chart-label" x="40" y="175">Blogs / publishing</text>
  <rect class="bar" x="220" y="158" width="426" height="24" rx="4" />
  <text class="chart-value" x="656" y="175">2568 ms</text>

  <text class="chart-label" x="40" y="225">Ecommerce</text>
  <rect class="bar" x="220" y="208" width="445" height="24" rx="4" />
  <text class="chart-value" x="675" y="225">2681 ms</text>

  <text class="chart-label" x="40" y="275">Finance</text>
  <rect class="bar" x="220" y="258" width="461" height="24" rx="4" />
  <text class="chart-value" x="691" y="275">2778 ms</text>

  <text class="chart-label" x="40" y="325">Travel</text>
  <rect class="bar" x="220" y="308" width="467" height="24" rx="4" />
  <text class="chart-value" x="697" y="325">2813 ms</text>

  <text class="chart-label" x="40" y="375">SaaS</text>
  <rect class="bar" x="220" y="358" width="516" height="24" rx="4" />
  <text class="chart-value" x="746" y="375">3108 ms</text>

  <text class="chart-note" x="220" y="402">Source: focused 39-site audit; median field LCP from CrUX.</text>
</svg>
<figcaption>Median mobile LCP by vertical in the focused 39-site audit.</figcaption>
</figure>
<h3>What the vertical split actually says</h3>
<p><strong>Ecommerce</strong> performed poorly not simply because commerce uses more images, but because the homepage is usually overburdened before the session has even begun. Promotional modules, merchandising, personalization, testing, analytics, and large JS bundles all compete for early attention. That is why ecommerce tends to generate simultaneous risk for LCP, INP, and CLS rather than failing only one metric.</p>
<p><strong>News and media</strong> showed a more complicated pattern. Request volume and total transfer were extreme, yet some field metrics remained respectable. In a small audit like this, that does not make the waterfall harmless. It suggests that mature publishers can offset some cost through caching, edge delivery, prioritization, or traffic patterns that a single lab run does not reproduce well. Even so, high request chains remain operationally fragile.</p>
<p><strong>SaaS</strong> was the weakest vertical on field Core Web Vitals in this sample, with <strong>0/6</strong> pages passing all three thresholds. That is notable because SaaS homepages often look cleaner than ecommerce surfaces. The issue is that “clean” design can still hide substantial runtime cost: long marketing pages, animation, product screenshots, chat, multi-product navigation, form logic, experimentation, and personalization all accumulate silently in startup work.</p>
<p><strong>Travel</strong> also failed all-three-CWV in every measured case. The likely explanation is interaction complexity. Travel homepages frequently prepare forms, calendars, destinations, maps, recommendations, pricing modules, and review surfaces early. They may look restrained above the fold, yet still carry heavy state and interaction cost underneath.</p>
<p><strong>Finance</strong> was mixed, but its recurring weakness was JavaScript rather than imagery. These pages often combine account-entry flows, security logic, compliance surfaces, personalization, product marketing, and analytics. The result is a page that may look visually conservative while remaining computationally expensive.</p>
<p><strong>Government</strong> was the clearest contrast. It was lighter, simpler, and more task-first almost across the board, and the metrics reflected that. The lesson is not that government sites are universally better designed. It is that narrowing the homepage’s job still buys real performance headroom.</p>
<p><strong>Blogs and publishing</strong> split cleanly into two models: simple text-first surfaces that perform well, and platform-like publishing homepages that behave more like product shells than reading environments. That split is a reminder not to treat “publishing” as a single technical category.</p>
<h2>Recurring failure modes</h2>
<h3>1. LCP failures are usually about startup sequencing, not just image compression</h3>
<p>Public data says images dominate LCP, and the audit echoed that: <strong>23/39</strong> measured pages had an image-classified LCP element. But the real issue is not “images are bad.” It is that teams often optimize the asset while leaving the discovery path, priority rules, script competition, and render delay untouched. In the audit, several pages with moderate image bytes still had slow field LCP, which suggests that startup sequencing often matters more than raw media weight.</p>
<h3>2. JavaScript remains the hidden tax on mobile responsiveness</h3>
<p>In the focused audit, <strong>19/39</strong> pages had field INP above 200 ms, <strong>19/39</strong> had lab TBT above 600 ms, and <strong>25/39</strong> transferred more than 1 MB of JavaScript. Those are not isolated outliers. They point to a common operating model in which every stakeholder gets a startup budget, but no one owns the total.</p>
<h3 id="chart-failure-modes">Where the audit shows the heaviest concentration of failure</h3>
<p>The strongest pattern in the audit is not a single bad metric. It is accumulation: slow LCP, large JavaScript payloads, heavy total transfer, and long external dependency chains often appear together rather than in isolation.</p>
<figure class="chart">
  <svg viewBox="0 0 900 430" role="img" aria-label="Most common failure patterns in the 39-site audit" aria-describedby="failure-desc">
    <desc id="failure-desc">Horizontal bars showing how many pages in the 39-site audit crossed specific risk thresholds for LCP, INP, JavaScript transfer, total transfer, external-domain requests, and CLS.</desc>

    <style>
      .bg { fill: transparent; }
      .axis { stroke: var(--faint, #9aa6b2); stroke-width: 1.2; }
      .grid { stroke: var(--line, #e4e8ee); stroke-width: 1; }
      .label { fill: var(--ink-2, #24303f); font-family: "IBM Plex Sans", system-ui, sans-serif; font-size: 14px; }
      .small { fill: var(--muted, #697585); font-family: "IBM Plex Mono", ui-monospace, monospace; font-size: 12px; }
      .value { fill: var(--ink, #0e1726); font-family: "IBM Plex Mono", ui-monospace, monospace; font-size: 13px; }
      .bar-high { fill: var(--orange-deep, #c2480f); }
      .bar-mid { fill: var(--orange, #ec5a1c); }
      .bar-low { fill: var(--navy, #0d1b2e); }
    </style>

    <rect class="bg" x="0" y="0" width="900" height="430" />

    <line class="axis" x1="260" y1="50" x2="260" y2="390" />
    <line class="axis" x1="260" y1="390" x2="840" y2="390" />

    <line class="grid" x1="260" y1="100" x2="840" y2="100" />
    <line class="grid" x1="260" y1="150" x2="840" y2="150" />
    <line class="grid" x1="260" y1="200" x2="840" y2="200" />
    <line class="grid" x1="260" y1="250" x2="840" y2="250" />
    <line class="grid" x1="260" y1="300" x2="840" y2="300" />
    <line class="grid" x1="260" y1="350" x2="840" y2="350" />

    <text class="small" x="260" y="408">0</text>
    <text class="small" x="395" y="408">10</text>
    <text class="small" x="530" y="408">20</text>
    <text class="small" x="665" y="408">30</text>
    <text class="small" x="800" y="408">39 pages</text>

    <text class="label" x="24" y="95">LCP above 2.5s</text>
    <rect class="bar-high" x="260" y="76" width="339" height="24" rx="4" />
    <text class="value" x="609" y="93">23 pages</text>

    <text class="label" x="24" y="145">INP above 200ms</text>
    <rect class="bar-mid" x="260" y="126" width="280" height="24" rx="4" />
    <text class="value" x="550" y="143">19 pages</text>

    <text class="label" x="24" y="195">More than 1 MB JS</text>
    <rect class="bar-high" x="260" y="176" width="369" height="24" rx="4" />
    <text class="value" x="639" y="193">25 pages</text>

    <text class="label" x="24" y="245">More than 3 MB total</text>
    <rect class="bar-mid" x="260" y="226" width="295" height="24" rx="4" />
    <text class="value" x="565" y="243">20 pages</text>

    <text class="label" x="24" y="295">More than 50 ext-domain reqs</text>
    <rect class="bar-high" x="260" y="276" width="398" height="24" rx="4" />
    <text class="value" x="668" y="293">27 pages</text>

    <text class="label" x="24" y="345">CLS above 0.1</text>
    <rect class="bar-low" x="260" y="326" width="236" height="24" rx="4" />
    <text class="value" x="506" y="343">16 pages</text>

    <text class="small" x="260" y="26">Threshold counts from the focused 39-site audit described in this article.</text>
  </svg>
  <figcaption>Pages crossing key performance-risk thresholds in the focused audit.</figcaption>
</figure>
<h3>3. CLS failures still come from predictable component behavior</h3>
<p><strong>16/39</strong> pages had CLS above 0.1, and <strong>8/39</strong> were above 0.25. The causes were not exotic. They were the usual suspects: media without reserved dimensions, banners, dynamic modules, consent UI, and late-loading components. CLS remains one of the clearest signs that layout rules are not enforced early enough in the component system.</p>
<h3>4. Total page weight is still a practical risk proxy</h3>
<p>The median lab transfer in the audit was about <strong>3.06 MB</strong>, with <strong>20/39</strong> pages above 3 MB and a heavy tail that extended far beyond that. Weight alone does not explain every bad experience, but large transfers tend to travel with other problems: more requests, more external domains, more scripts, and more ways to delay meaningful rendering or block the main thread.</p>
<h3>5. External-domain dependency chains are part of the performance budget</h3>
<p><strong>27/39</strong> pages made more than 50 external-domain requests. Even allowing for the fact that this proxy includes some owned infrastructure, the pattern is hard to ignore. More domains mean more connection setup, more scheduling complexity, more cache variability, and more systems that can degrade the page without the core team shipping a line of new product code.</p>
<h2>What the public data and the focused audit agree on</h2>
<ul>
<li>LCP remains the main mobile weakness.</li>
<li>Images are central to LCP, but rarely the whole story.</li>
<li>JavaScript is still the main execution-cost problem.</li>
<li>Third-party and adjacent dependency chains are normal, not exceptional.</li>
<li>Simpler task-first pages retain a measurable advantage.</li>
<li>Homepages remain harder than secondary pages because more priorities collide there.</li>
</ul>
<p>The focused audit produced worse pass rates than public CrUX origin-level data: only <strong>11/39</strong> measured pages passed all three field thresholds, versus <strong>56.4%</strong> of origins in CrUX April 2026. That is not a contradiction. The audit was small, homepage-only, and intentionally skewed toward recognizable, complex public entry pages. If anything, the gap helps explain why many teams feel performance is “supposed to be improving” while their most visible surfaces still feel fragile.</p>
<h2>Practical recommendations</h2>
<h3>1. Treat LCP as an architecture decision, not an image task</h3>
<p>For every important mobile template, identify the likely LCP element and verify that it is discoverable early, not lazy-loaded, assigned appropriate priority, and not delayed by CSS, hydration, or script-heavy startup work. If the main content is not clearly winning the first few seconds, the page is already negotiating against itself.</p>
<h3>2. Define a JavaScript budget that includes third-party code</h3>
<p>A budget that covers only first-party bundles is not a real mobile budget. Track first-party JS, third-party JS, long tasks, TBT, field INP, and vendor-level ownership. If no one can say who owns the startup cost of experimentation, chat, consent, analytics, or personalization, then no one really owns mobile responsiveness.</p>
<h3>3. Keep field outcomes and lab diagnostics separate</h3>
<p>Use CrUX or RUM to understand user outcomes. Use Lighthouse, DevTools, traces, and similar tools to debug causes. The distinction matters because overreacting to one lab run can be as misleading as ignoring it. Good performance work depends on keeping outcome metrics and causal diagnostics in dialogue, not treating them as the same evidence.</p>
<h3>4. Make layout stability a component-system rule</h3>
<p>CLS prevention should live in the design system and CMS, not in occasional clean-up passes. Media components should require dimensions or aspect ratio. Ad slots should reserve space. Consent and promo surfaces should avoid pushing content unexpectedly. Recommendation modules should have placeholders. If layout stability depends on late vigilance, it will eventually fail.</p>
<h3>5. Audit external dependencies like product features</h3>
<p>Every external dependency should have an owner, a business purpose, a loading strategy, and a removal path. Teams should regularly ask whether a script belongs before LCP, after interaction, after consent, or off the homepage entirely. If an external system is allowed to consume startup budget indefinitely without review, it is effectively shipping product policy by default.</p>
<h3>6. Set budgets by surface type, not only site-wide averages</h3>
<table>
<thead>
<tr>
<th>Surface type</th>
<th>Suggested mobile priority</th>
</tr>
</thead>
<tbody>
<tr>
<td>Government / public service</td>
<td>Keep text-first, minimize JS, avoid unnecessary external dependencies</td>
</tr>
<tr>
<td>Ecommerce homepage</td>
<td>Prioritize hero/product LCP, limit early personalization, reserve promo and product image space</td>
</tr>
<tr>
<td>News/media homepage</td>
<td>Control ad and analytics waterfalls, isolate embeds, monitor external request growth</td>
</tr>
<tr>
<td>SaaS marketing</td>
<td>Cap animation and hydration cost, simplify above-the-fold sections</td>
</tr>
<tr>
<td>Travel search</td>
<td>Delay non-critical widgets, isolate calendar and map cost, protect INP explicitly</td>
</tr>
<tr>
<td>Finance</td>
<td>Audit JS boot cost, defer non-auth-critical scripts, measure responsiveness under real interaction</td>
</tr>
<tr>
<td>Publishing platforms</td>
<td>Separate lightweight reading surfaces from heavy feed/onboarding surfaces</td>
</tr>
</tbody>
</table>
<h2>Limitations</h2>
<p>This article has several important limits. Public datasets do not all measure the same thing. Web Almanac combines HTTP Archive crawl data and CrUX field data. CrUX reflects real Chrome users for eligible pages and origins. PageSpeed Insights may show URL-level or origin-level field data depending on availability, and Lighthouse remains a simulated lab diagnostic rather than a field outcome.</p>
<p>The focused audit is also narrow by design. It covers 39 public homepages, not product pages, article pages, search flows, checkout, logged-in experiences, or repeat visits. It uses one lab run per page, so lab metrics should be read as directional rather than stable. It also mixes field and lab evidence deliberately: LCP, INP, and CLS are field values from CrUX, while TBT, transfer size, request counts, and Lighthouse scores come from a single controlled run.</p>
<p>External-domain request counts are a proxy, not a perfect vendor census. They include independent third parties, but may also include owned CDNs, image hosts, static asset domains, and adjacent infrastructure. The useful interpretation is dependency pressure, not a literal count of external vendors.</p>
<p>Finally, homepages are volatile. Promotions, campaigns, breaking news, experimentation, consent state, CDN behavior, device class, and network quality can all change the observed result.</p>
<h2>Conclusion</h2>
<p>The mobile web in 2026 is not stagnant. Core Web Vitals pass rates have improved, INP looks stronger, and CLS is materially better than it was a few years ago. But the deeper performance story is less flattering. LCP still lags. Page weight keeps rising. JavaScript remains expensive. Third-party and external-domain dependency chains are built into the modern homepage by default.</p>
<p>The focused 39-site audit reinforces that point without pretending to be universal. Complex commercial homepages continue to repeat the same failure modes: image-led LCP candidates, crowded startup paths, large JS payloads, long request chains, dependency sprawl, and layout shifts from dynamic UI.</p>
<p>The practical lesson is that mobile performance is no longer mainly a documentation problem. Teams generally know what good practice looks like. The harder question is governance: who gets to spend the first few seconds of the mobile experience, and who says no when the budget is already gone? Until that question is answered more rigorously, aggregate web metrics may keep improving while many real homepages continue to feel slower and more fragile than they need to be.</p>
<h2>Sources</h2>
<ul>
<li><a href="https://almanac.httparchive.org/en/2025/performance" target="_blank" rel="noopener">HTTP Archive Web Almanac 2025: Performance</a></li>
<li><a href="https://almanac.httparchive.org/en/2025/page-weight" target="_blank" rel="noopener">HTTP Archive Web Almanac 2025: Page Weight</a></li>
<li><a href="https://almanac.httparchive.org/en/2025/third-parties" target="_blank" rel="noopener">HTTP Archive Web Almanac 2025: Third Parties</a></li>
<li><a href="https://almanac.httparchive.org/en/2025/ecommerce" target="_blank" rel="noopener">HTTP Archive Web Almanac 2025: Ecommerce</a></li>
<li><a href="https://developer.chrome.com/docs/crux" target="_blank" rel="noopener">Chrome UX Report documentation</a></li>
<li><a href="https://developer.chrome.com/docs/crux/release-notes" target="_blank" rel="noopener">Chrome UX Report release notes</a></li>
<li><a href="https://web.dev/articles/vitals" target="_blank" rel="noopener">web.dev: Core Web Vitals</a></li>
<li><a href="https://web.dev/articles/optimize-lcp" target="_blank" rel="noopener">web.dev: Optimize Largest Contentful Paint</a></li>
<li><a href="https://web.dev/articles/optimize-inp" target="_blank" rel="noopener">web.dev: Optimize Interaction to Next Paint</a></li>
<li><a href="https://web.dev/articles/optimize-cls" target="_blank" rel="noopener">web.dev: Optimize Cumulative Layout Shift</a></li>
<li><a href="https://web.dev/articles/cookie-notice-best-practices" target="_blank" rel="noopener">web.dev: Cookie notice best practices</a></li>
<li><a href="https://web.dev/articles/optimizing-content-efficiency-loading-third-party-javascript" target="_blank" rel="noopener">web.dev: Optimizing third-party JavaScript</a></li>
<li><a href="https://developers.google.com/speed/docs/insights/v5/about" target="_blank" rel="noopener">Google PageSpeed Insights documentation</a></li>
</ul>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>