<?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, 31 Oct 2026 14:52:45 +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>Korea’s App Payment Ruling and Web Checkout</title>
		<link>https://www.mobiletech.mobi/korea-app-payments-web-checkout-ruling/</link>
		
		<dc:creator><![CDATA[mobiletech_admin]]></dc:creator>
		<pubDate>Sat, 31 Oct 2026 14:52:45 +0000</pubDate>
				<category><![CDATA[Apps vs Web]]></category>
		<guid isPermaLink="false">https://www.mobiletech.mobi/?p=545</guid>

					<description><![CDATA[South Korea ordered Google and Apple to improve payment choice. What current platform rules mean for web checkout, fees and customer support.]]></description>
										<content:encoded><![CDATA[<p>South Korea’s app-market regulator has ordered Google and Apple to change practices that it says restrict developers’ payment choices, while also fining the companies a combined KRW 63.71 billion. The important part for mobile product teams is not the fines themselves. It is the distinction the decision draws between a nominal choice of payment processor and a purchase path that developers can realistically use.</p>
<p>On October 28, the Korea Media and Communications Commission, or KMCC, said Google and Apple had violated the country’s Telecommunications Business Act by effectively forcing particular payment methods, unfairly delaying some app reviews and, in Apple’s case, historically applying commissions differently to domestic and overseas developers. The regulator imposed a KRW 42.9 billion penalty on Google and KRW 20.81 billion on Apple and ordered both companies to improve relevant business procedures. The <a href="https://www.kmcc.go.kr/user.do?boardId=1058&amp;boardSeq=69632&amp;cp=1&amp;dc=E04010000&amp;mode=view&amp;page=E04010000">KMCC’s English-language announcement</a> describes this as the first enforcement action under the 2021 amendments that added specific prohibitions for app-market operators.</p>
<p>For a publisher or app developer, however, “external payment” can describe several very different technical experiences. A developer might process payment with another provider while the customer remains inside the native app. The app might show a web checkout inside an embedded browser view. Or it might send the customer into the device’s normal browser to complete a purchase on the developer’s website. Those flows differ in implementation, platform rules, user experience, fees and responsibility for support.</p>
<p>The Korean decision should therefore not be read as a switch that suddenly enables every app to put a “Buy on our website” button wherever it wants. Current Google Play and App Store documentation still imposes region-specific rules, and some of the policies announced for South Korea have not yet reached their scheduled rollout dates.</p>
<h2>Why the regulator rejected payment choice that existed mostly on paper</h2>
<p>KMCC’s finding focuses on the difference between offering another payment mechanism in theory and making it a practical alternative.</p>
<p>According to the commission, Google and Apple had allowed their own in-app purchasing systems and third-party payment systems implemented by developers, but restricted other mechanisms such as external payments. KMCC also singled out arrangements under which developers choosing third-party in-app payment could still face a 26% commission while taking on additional work and costs themselves.</p>
<p>The commission said the two companies imposed conditions that reduced the usefulness of those alternatives. It cited Google restrictions involving the display of prices by payment method and Apple’s requirement for a separate app submission. Combined with the developer’s own payment-processing, settlement and engineering costs, KMCC concluded that these conditions could have the practical effect of steering developers back toward the platform’s own in-app purchase system.</p>
<p>That 26% figure needs careful interpretation. It describes the arrangements criticized in the enforcement decision; it is not a new commission that KMCC has ordered developers to pay for web checkout. The regulator’s release does not establish a new web-payment fee percentage, nor does it announce a deadline by which every external-link purchase flow must become available.</p>
<h2>Third-party billing inside an app is not the same as buying on a website</h2>
<p>This distinction is particularly clear in Apple’s current South Korean documentation.</p>
<p>Apple’s existing <a href="https://developer.apple.com/support/storekit-external-entitlement-kr">StoreKit External Purchase program for South Korea</a> lets an eligible developer use another payment service provider for purchases inside an iOS or iPadOS app. But the developer must request an entitlement, submit a separate binary distributed only through the South Korean App Store and follow Apple’s required StoreKit flow.</p>
<p>The current documentation lists KCP, Inicis, Toss and NICE as pre-approved payment service providers, although Apple says another provider can be submitted for consideration. The entitlement is tied to a South Korea-specific app binary and cannot currently be used in that same app alongside Apple’s own in-app purchase system.</p>
<p>Most importantly for an app-to-web strategy, Apple’s documented Korean flow is primarily a third-party <em>in-app</em> payment flow. Apple says it must provide a native experience and may not run inside a web view. The customer may leave the app only when doing so is legally required to complete the purchase on a website or in another app.</p>
<p>That is very different from a general-purpose button that sends a customer to a conventional mobile checkout page in Safari.</p>
<p>Apple also requires a disclosure before the external payment flow. The notice tells users that purchases will be handled by the developer rather than Apple and that App Store features such as Apple-managed subscription management, Ask to Buy, Family Sharing and Apple-handled refund requests will not be available for those transactions.</p>
<p>The practical consequence is that changing the payment processor changes more than the checkout screen. It can move responsibility for subscription management, refunds, payment support, fraud handling and transaction records from the app store to the developer.</p>
<h2>Google already documents a web-based option in South Korea, but it has specific constraints</h2>
<p>Google Play’s current South Korean rules expose a somewhat different path.</p>
<p>Google says in its <a href="https://support.google.com/googleplay/android-developer/answer/11222040?hl=en">South Korea alternative-billing documentation</a> that developers can offer an alternative billing system alongside Google Play Billing to mobile and tablet users in the country. Developers must enroll through Play Console, integrate Google’s alternative-billing APIs, meet trust and safety requirements and report authorized alternative transactions to Google within 24 hours.</p>
<p>The same documentation explicitly addresses web payments. Google says developers can link out within an app to present web-based payment as an alternative for South Korean users. However, its currently documented implementation requires that web payment option to be presented inside the app through an embedded web view rather than simply opening an unrestricted external browser flow.</p>
<p>So even though the checkout uses web technology, the user journey is still governed by the app-distribution program. From a product architecture perspective, that is closer to “web checkout embedded in an app-controlled purchase flow” than to “the app sends the user to the ordinary ecommerce site.”</p>
<p>Google’s present Korean program also retains a Play service fee. Its documentation says that when the user chooses alternative billing, the developer’s applicable Play service fee is reduced by four percentage points compared with the fee for the equivalent Google Play Billing transaction.</p>
<p>That, again, should not be confused with the 26% figure in KMCC’s enforcement announcement. The regulator was describing the conditions it investigated. Google’s currently published developer rules describe the program developers can actually enroll in today.</p>
<h2>Google has announced another fee model for Korea, but it is not in force yet</h2>
<p>There is an additional timing complication for teams planning changes now.</p>
<p>In March 2026, Google announced a broader restructuring of Play service fees and billing choice. Its current <a href="https://support.google.com/googleplay/android-developer/answer/16954621?hl=en">lower service fee documentation</a> says the model is being introduced region by region rather than globally at once.</p>
<p>South Korea is scheduled for December 31, 2026. As of October 31, that date is still in the future.</p>
<p>Under the announced model, Google says service fees will apply whether a qualifying transaction uses Google Play Billing, alternative billing or an external web link, with different rates depending in part on whether the relevant Play install is considered new or existing. Google’s documentation also notes that details of the external-web-link program are still to be supplied for parts of this rollout.</p>
<p>That means a team making a Korean checkout decision today has to separate three states:</p>
<ul>
<li><strong>What is available now:</strong> the existing South Korean alternative-billing program and its current implementation and reporting rules.</li>
<li><strong>What Google has announced:</strong> a broader billing-choice and service-fee model scheduled to reach South Korea on December 31, 2026.</li>
<li><strong>What KMCC has ordered:</strong> corrective changes to practices that the regulator found unlawfully restricted payment choice.</li>
</ul>
<p>Those are related developments, but they are not interchangeable.</p>
<h2>Apple’s present Korean program also still charges 26%</h2>
<p>Apple’s current developer documentation shows why teams should verify live platform rules instead of assuming that the regulator’s order has already rewritten commercial terms.</p>
<p>Under Apple’s existing South Korea third-party payment entitlement, Apple says it charges a <a href="https://developer.apple.com/support/storekit-external-entitlement-kr">26% commission on the price paid by the user, gross of value-added tax</a>. Developers must report qualifying sales to Apple monthly and remain responsible for their own payment provider and customer support.</p>
<p>This is the same general arrangement that KMCC criticized when explaining why merely permitting third-party in-app processing did not necessarily provide meaningful payment choice.</p>
<p>The October 28 enforcement announcement says Apple has separately described voluntary improvements to the Korean app-market transaction environment, but KMCC’s release does not give enough implementation detail to treat those improvements as a new production-ready checkout policy. A team should therefore rely on the developer terms and entitlements that Apple has actually published for South Korea, then re-check them as Apple implements any changes resulting from the order.</p>
<h2>An app-to-web checkout needs more than permission to show a link</h2>
<p>For product teams, the technical question is usually framed too narrowly: “Are we allowed to send users to the web?” The more useful question is: “Can the user move from the app to a web transaction and back to a working account without losing context, trust or subscription state?”</p>
<p>A viable app-to-web flow has several independent pieces.</p>
<h3>Identity must survive the channel change</h3>
<p>If the user is signed into the app, the website needs a reliable way to identify the same account without creating an insecure token handoff or forcing the customer through an unexpected registration loop. A web payment that lands on the wrong account can be worse than no alternative payment option at all.</p>
<h3>The purchase has to unlock the correct entitlement</h3>
<p>A successful card authorization is not the end of the transaction. The developer’s backend needs to grant the purchased subscription, credits or digital feature to the account that the app will use. The app then needs a dependable way to refresh that entitlement.</p>
<p>This is especially important when one product can be purchased through several billing systems. The backend, not the visual checkout button, becomes the place where those parallel purchase histories have to converge.</p>
<h3>Subscription management moves somewhere visible</h3>
<p>If the platform no longer manages the transaction, users need a clear place to update a payment method, cancel, see renewal terms and request support. Both Apple and Google warn that alternative billing can remove some store-managed purchase features.</p>
<p>That changes UX copy inside the app. A “Manage subscription” control cannot blindly open the App Store or Google Play if the account might instead be billed directly by the developer.</p>
<h3>Refunds and customer service must follow the payment path</h3>
<p>A customer usually does not care which billing architecture a product team chose. The customer only knows that money was charged.</p>
<p>If some purchases are handled by Apple, some by Google and some by a developer’s web payment provider, support systems need to identify the transaction owner quickly. Otherwise, payment choice creates a support maze in which the app store sends the customer to the developer and the developer sends the customer back to the store.</p>
<h2>Do not assume a normal website link and an embedded web checkout are equivalent</h2>
<p>From a web-development perspective, both may ultimately render HTML, but the operational differences are substantial.</p>
<p>An embedded web view lives inside the native app’s process and navigation. It can be constrained by platform requirements, app-review rules and a platform-defined disclosure flow. A purchase opened in the device’s normal browser behaves more like an independent web session: it can use normal browser navigation, existing website authentication, saved web credentials and other browser capabilities.</p>
<p>Those differences affect analytics as well. A product team measuring an app-to-web funnel has to distinguish at least the app CTA, successful arrival at checkout, authenticated account, payment completion and successful entitlement delivery back to the app. Treating all of that as one “checkout conversion” obscures where users are actually dropping out.</p>
<p>It also matters for accessibility and reliability. A responsive checkout page that works well in Chrome or Safari is not automatically proven to work correctly in every embedded web view configuration a store program may require.</p>
<h2>The app-review portion of the decision matters to release planning too</h2>
<p>Payments were not the only issue in KMCC’s ruling.</p>
<p>The commission also found that both Google and Apple had unfairly delayed some app reviews by failing to provide sufficiently specific reasons for delays or clear answers about expected schedules when developers asked about them.</p>
<p>That matters because a new payment architecture often requires a new app submission, new entitlement configuration or both. Apple’s current Korean alternative-payment program, for example, requires a separate South Korea-only binary and review. Its own documentation notes that an incomplete submission or a payment provider that has not already been approved can delay review or result in rejection.</p>
<p>KMCC has ordered improvements to review procedures, but teams should not translate that into an assumption of instant or guaranteed approval. The practical change to watch is whether the stores provide clearer, more predictable review handling as they respond to the order.</p>
<h2>Apple’s historical commission issue is a separate finding</h2>
<p>The Korean decision also includes an Apple-specific violation that should not be mixed into the current external-payment debate.</p>
<p>KMCC found that from July 2015 through January 2023 Apple calculated its 30% commission differently for Korean and overseas developers. According to the regulator, overseas developers were charged based on the consumer price excluding value-added tax, while domestic developers were charged based on the final consumer price including VAT.</p>
<p>KMCC classified that as discriminatory treatment and ordered Apple to prevent a recurrence. It is a historical commission-calculation finding, not a statement that every current Apple transaction in Korea carries a 30% fee and not a new rule for web checkout.</p>
<h2>What a team should verify before changing checkout</h2>
<p>The Korean ruling makes payment choice more important, but it also makes precise implementation more important. Before shipping an app-to-web or alternative-billing flow, a team should verify the live rules for the exact storefront, platform and product rather than extrapolating from a regulatory headline.</p>
<ul>
<li><strong>Confirm the user’s storefront and device scope.</strong> Google’s Korean rules refer to mobile and tablet users; Apple’s existing entitlement is specifically for iOS and iPadOS apps in the South Korean App Store.</li>
<li><strong>Identify the actual permitted flow.</strong> Determine whether the current program allows native third-party billing, an embedded web checkout, a link into the external browser, or some combination.</li>
<li><strong>Check whether platform billing must remain available.</strong> Current programs differ on whether the developer can combine the store’s payment system with another payment method.</li>
<li><strong>Verify current fees, not headline percentages.</strong> Google has a Korean model in effect now and another scheduled for December 31. Apple’s currently published Korean third-party payment entitlement still specifies a 26% commission.</li>
<li><strong>Map transaction reporting requirements.</strong> Alternative payments can still require reporting to the app marketplace even when the marketplace did not process the card payment.</li>
<li><strong>Test account, entitlement and cancellation flows.</strong> Payment completion is useful only if the customer can immediately use and later manage what was purchased.</li>
<li><strong>Plan customer support ownership.</strong> The developer may become responsible for refunds, subscription management and payment disputes that the store previously handled.</li>
<li><strong>Re-check policies before release.</strong> The October 28 order requires corrective changes, and Google already has a Korean policy transition scheduled for the end of 2026.</li>
</ul>
<h2>Payment choice is becoming a systems question, not just a checkout question</h2>
<p>The South Korean decision is significant because the regulator is challenging not merely whether a button labeled “alternative payment” exists, but whether the commercial and procedural conditions around it leave developers with meaningful choice.</p>
<p>For mobile-web teams, that shifts attention toward the entire transaction path. A website can become the checkout layer for an app, but only when store rules permit the particular handoff and the developer has built the surrounding account, entitlement, support and reporting systems.</p>
<p>Google’s current Korean policy already recognizes a web-based payment path, albeit with a specifically controlled in-app web experience. Apple’s current Korean alternative-payment program remains structured around an approved third-party payment provider inside a South Korea-only app binary. Meanwhile, regulatory orders and already announced platform changes may alter those arrangements again.</p>
<p>The safest conclusion on October 31 is therefore not that Korean apps can now freely bypass app-store billing. It is that South Korea is pushing the platforms toward more substantive payment choice, while the exact app-to-web path still depends on the developer rules that are actually in force when a team ships.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>PhonePe Adds Digital-Goods Shopping to the Web</title>
		<link>https://www.mobiletech.mobi/phonepe-appstore-digital-goods-webstore/</link>
		
		<dc:creator/>
		<pubDate>Fri, 30 Oct 2026 09:25:55 +0000</pubDate>
				<category><![CDATA[Apps vs Web]]></category>
		<guid isPermaLink="false">http://localhost:18082/?p=541</guid>

					<description><![CDATA[PhonePe adds a browser storefront for digital goods. See how gift-card purchases differ from app downloads and the top-ups still on its roadmap.]]></description>
										<content:encoded><![CDATA[<p>PhonePe is giving its app marketplace a second job: selling digital goods to people arriving through a browser. In its <a href="https://www.phonepe.com/press/indus-appstore-transforms-to-phonepe-appstore-introducing-a-unified-marketplace-for-indias-digital-goods">October 28, 2026 announcement</a>, the company renamed its India-focused Indus Appstore to PhonePe Appstore and introduced a webstore for desktop and mobile browsers. PhonePe says customers can discover and purchase digital goods without downloading a dedicated marketplace app.</p>
<p>For publishers and product teams, that creates another place to send someone who wants to buy a specific item. The immediate examples are gift cards, with their own redemption steps. In the announcement, PhonePe placed a separate Digital Top-ups feature on its roadmap. The distinction matters because buying digital value, redeeming it and downloading an app are different tasks.</p>
<h2>What is already on the web</h2>
<p>As of October 29, the <a href="https://www.phonepeappstore.com/giftcards/">gift-card catalog</a> includes Amazon Pay and Flipkart cards alongside Steam Wallet cards and Minecraft Minecoins gift cards. “Digital goods” here covers vouchers used with retailers and services as well as gaming products. Each listing has its own destination and redemption process.</p>
<p>The <a href="https://www.phonepeappstore.com/giftcards/game/steam_wallet_card">Steam Wallet Card page</a> makes the purchase path concrete. It offers a choice of card values and a payment prompt. Its redemption instructions then send the buyer to Steam to sign in, enter the gift-card code and add the value to a Steam Wallet. Buying the card and making its value available in Steam are separate steps.</p>
<p>The <a href="https://www.phonepeappstore.com/giftcards/shopping/flipkart">Flipkart E-Gift Card listing</a> documents a different destination: select products on Flipkart, choose gift-card payment at checkout, and enter the card number and PIN. The listing gives the card a one-year validity period. The browser storefront sells a voucher; the retailer is where the customer subsequently uses it.</p>
<p>Those details matter more than a discount badge. A buyer needs to know where the value can be used, whether it expires and what is required to redeem it. The <a href="https://www.phonepeappstore.com/giftcards/finance/amazon_pay">Amazon Pay card’s published conditions</a>, for example, restrict redemption to eligible purchases and say that unused balances cannot be transferred to another user’s account. Conditions belong to the individual product, so one card’s rules should not be assumed to cover the whole store.</p>
<h2>The app still has its own delivery path</h2>
<p>Indus Appstore began as an Android app marketplace, with its <a href="https://www.phonepe.com/press/phonepe-unveils-indus-appstore-a-game-changer-in-indias-digital-journey/">consumer launch on February 21, 2024</a>. The new name and shopping channel expand what the marketplace does. They do not establish a new way for its native apps to run on the web.</p>
<p>The current <a href="https://www.phonepeappstore.com/en/consumer-tnc/">PhonePe Appstore terms</a> continue to describe the native marketplace, its mobile apps and compatible devices in Android terms. Browser access to a digital-goods shop is a separate capability. Being able to open a product page on a computer does not establish that the corresponding mobile app can run on that computer.</p>
<p>The <a href="https://www.phonepeappstore.com/apps/entertainment/sony-liv/com.sonyliv/?id=com.sonyliv&amp;page=details">Sony LIV app listing</a> shows the distinction in the interface. It presents screenshots, a download size, permissions and developer information, then directs visitors to scan to download through PhonePe Appstore. That is an app-discovery page leading toward installation. It is a different task from buying a gift card through a web product page.</p>
<p>For a developer, the practical implication is that the place where someone buys digital value can be different from the place where they use the product. Selling a voucher on the web does not require moving gameplay or other app features into a browser. Installation, compatibility and updates remain questions for the app’s distribution channel.</p>
<h2>A more direct destination for a purchase link</h2>
<p>PhonePe’s stated benefit is access to digital-goods shopping without downloading the marketplace app. That gives a publisher a straightforward option: a product guide, promotion or support article can link to the relevant web listing. Someone interested in a Steam card can arrive at that card’s description and redemption instructions, with the purchase action on the same page.</p>
<p>The value of that arrangement is the match between the link and the reader’s task. A page about buying credit can lead to the credit product. A page about installing software can lead to the app listing. Both destinations can exist in the same marketplace while serving different purposes.</p>
<p>Whether this produces more completed purchases requires transaction data. The announcement supplies no conversion result to apply to another business, and a public payment prompt reveals little about how well the full purchase and redemption journey works.</p>
<p>For teams evaluating the channel, the useful question is whether a customer can get from the specific product link to usable value in the intended service. A page visit, a successful payment and a redeemed code are distinct milestones. The Steam instructions make that last step especially clear: the customer’s task continues after the marketplace purchase.</p>
<h2>What PhonePe has promised for Digital Top-ups</h2>
<p>PhonePe’s announcement explicitly places <a href="https://www.phonepe.com/press/indus-appstore-transforms-to-phonepe-appstore-introducing-a-unified-marketplace-for-indias-digital-goods">Digital Top-ups in its feature pipeline</a>, with gaming first. It describes planned purchases of in-game currency and items, dating-app boosts and technology-related top-ups, but gives no launch date for that feature.</p>
<p>The existing gaming gift cards therefore should not be treated as proof that the announced top-up rollout is complete. A Steam Wallet code already has a documented redemption process. Listings for products such as Minecoins also identify themselves as gift cards. Those are existing products with specific descriptions; the broader feature announcement describes what PhonePe intends to add.</p>
<p>The announcement does not explain whether future top-ups will use codes, account identifiers or another fulfillment method. That leaves a material question for game developers: how will a purchase reach the correct player, and what will the player need to do? A studio planning around the new feature needs supported-title information and fulfillment documentation before treating it as an established sales channel.</p>
<h2>What the 200 million devices figure tells a business</h2>
<p>In the same announcement, <a href="https://www.phonepe.com/press/indus-appstore-transforms-to-phonepe-appstore-introducing-a-unified-marketplace-for-indias-digital-goods">PhonePe reports a presence on more than 200 million devices</a>, driven by partnerships with device manufacturers. That is a statement about device presence. It is not a count of active shoppers, paying customers or visitors to the new webstore.</p>
<p>The distinction changes how a product team should assess the opportunity. A large installed footprint can explain interest in the marketplace, but it cannot supply a web sales forecast. A forecast needs evidence about the relevant audience and completed transactions in the channel being considered. The browser storefront will need to demonstrate that performance on its own terms.</p>
<h2>Follow the purchase through to fulfillment</h2>
<p>The gift-voucher section of <a href="https://www.phonepeappstore.com/en/consumer-tnc/">PhonePe Appstore’s consumer terms</a> describes the platform as an intermediary for vouchers supplied by third parties. It directs users to product-specific conditions and the relevant provider or merchant for voucher issues. For a publisher recommending a product, that makes the redemption destination and support route part of the information a reader needs before paying. The terms also do not represent the service as intended for use outside India. An accessible product page alone does not establish that a voucher will work with an international reader’s account.</p>
<p>PhonePe also presents the expanded marketplace as an opportunity for developers to improve margins. That is the company’s proposition; the October announcement does not provide a merchant fee schedule or a calculation showing those savings. A business assessing participation still needs the commercial terms for the particular product and arrangement.</p>
<p>The useful lesson for a team <a href="https://www.mobiletech.mobi/apps-vs-web/">choosing how to use apps and the web</a> is to assign each channel a specific job. A web page can handle discovery and the purchase of a voucher, the relevant brand or service can handle redemption, and an installed app can remain where the customer uses the product. PhonePe’s current listings make that division visible. Its planned Digital Top-ups feature will need equally clear answers about what is sold and how it reaches the buyer.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Accessibility Starts Before the Final Audit</title>
		<link>https://www.mobiletech.mobi/mobile-accessibility-design-workflow-study/</link>
		
		<dc:creator/>
		<pubDate>Wed, 28 Oct 2026 15:03:48 +0000</pubDate>
				<category><![CDATA[Mobile UX]]></category>
		<guid isPermaLink="false">http://localhost:18082/?p=538</guid>

					<description><![CDATA[A study of eight mobile app creators reveals overlooked accessibility work. Here is what small web teams can learn—and what the evidence cannot prove.]]></description>
										<content:encoded><![CDATA[<p>A new study of eight mobile app creators finds that accessibility work can be happening before anyone calls it accessibility. In <a href="https://dl.acm.org/doi/10.1145/3797867.3829058">research published by ACM on October 24, 2026</a>, participants described improving readability and making interfaces easier to operate, sometimes while reporting that they had postponed accessibility or had not considered it that day.</p>
<p>The researchers also found an uneven response to constraints. Participants could welcome limited screen space as a useful creative challenge, yet regard accessibility requirements as an extra obligation. Their accounts suggest that when a requirement arrives, how it is presented, and whether the designer has support all help shape that response.</p>
<p>For a team responsible for a mobile website, the practical question is how an accessibility decision makes it from an early sketch into something that gets implemented and checked. This study offers useful clues about that process. Its evidence comes from mobile app creators; applying those clues to website work requires judgment.</p>
<h2>What the researchers followed</h2>
<p>The paper, <em>From Compliance to Creativity: A Longitudinal Study of How Accessibility Constraints Impact Early-Career, Small-Company Mobile Designers</em>, is by Anisa Callis, Sarah Andrew, Maya Lane, Garreth W. Tigwell, and Anne Spencer Ross. Published for ASSETS 2026, it follows eight U.S. mobile app creators through two weeks of diary entries and subsequent interviews or email follow-ups.</p>
<p>The group included six designers and two developers, ages 19 to 35. Seven worked at companies with fewer than 50 employees; one freelanced. Together they produced 90 diary entries about actual projects, describing their tasks, tools, creative decisions, collaboration, and attention to accessibility. Six participants completed live follow-up interviews, while two answered by email.</p>
<p>The diaries let researchers compare what participants reported doing on a particular day with how they explained that work afterward. The findings concern creators’ accounts of their work: the researchers did not audit the accessibility of the resulting apps or track any participant through an entire project from conception to deployment.</p>
<h2>The work was there. The label was missing.</h2>
<p>One developer described working on readability, reconsidering a dark background, and adding a choice of themes for a wider range of users. Yet that diary entry marked accessibility as postponed. In the follow-up, the developer explained that font color and font size had fallen outside their working definition of accessibility.</p>
<p>A designer working on software for field crews described larger buttons, clearer labels, and a supply-request flow that could be used with one hand. The designer recognized these as changes that helped a broad audience, while distinguishing them from what they considered formal accessibility support. Other participants similarly described inclusive or user-focused decisions without consistently connecting them to accessibility.</p>
<p>The distinction matters for a team trying to build on its own work. If a review only asks whether an accessibility task was completed, it may miss relevant decisions already made during ordinary design. A more useful follow-up is to name the need that decision addresses and identify what still needs verification.</p>
<p>A theme switch, for example, is a design choice whose accessibility properties still require checking. Recognizing the intention does not establish that either theme has adequate contrast. The researchers themselves distinguish broad usability work from technical accessibility requirements that familiar design habits may leave uncovered.</p>
<h2>How constraints felt during design</h2>
<p>Participants’ accounts complicate the idea that accessibility necessarily limits creative options. One designer reported that working with accessible color palettes suggested brand colors they had not previously considered. Another described preserving contrast while experimenting with translucent interface styling. In those examples, accessibility was part of developing the visual idea.</p>
<p>Later, contrast cleanup could feel very different. One participant described design-system checking as “less about creativity and more about discipline and precision.” Deadline pressure also narrowed exploration: participants described concentrating on getting basic features ready for a demonstration while accessibility slipped further down the schedule.</p>
<p>The study did not experimentally vary the timing of requirements or measure the originality of the resulting designs. What it captures is how participants experienced the work. Early requirements sometimes gave exploration a direction; poorly timed demands and missing knowledge could make it feel like additional cleanup.</p>
<p>Wireframes were an especially revealing example. Participants already made decisions about navigation, hierarchy, labels, motion, and touch areas while working at low fidelity. Some nevertheless marked accessibility as irrelevant or unscheduled during wireframing sessions. A sketch does not need finished colors to raise a useful question about whether a task is understandable or a control can be found.</p>
<p>Documentation created another weak point. One participant reported that inconsistent component names and accessibility notes made it harder to tell which elements had already been checked for contrast. For a small team, this is a concrete handoff problem: a design decision needs enough context to survive the move from a design file to implementation and testing.</p>
<h2>The diary became part of the workflow</h2>
<p>Perhaps the paper’s most interesting observation is also one of its methodological limits. Researchers identified six participants whose accounts indicated that taking part in the study prompted changes in their accessibility consideration or practice. Examples included renewed attention to screen reader testing, more deliberate use of accessibility checkers, and recognizing previously unnamed design decisions.</p>
<p>Those accounts make reflection worth investigating. They do not establish how often it would help other teams, whether the changes lasted, or whether users encountered fewer barriers afterward. There was no control group and no independent measurement of improved accessibility outcomes. The authors explicitly call for a controlled study of structured reflection.</p>
<p>The questions may also have shaped what participants reported. Creativity and accessibility appeared in separate diary questions, potentially reinforcing the separation the researchers were examining. Repeatedly asking about accessibility could change both participants’ attention and their descriptions of their work. This is diary reactivity: the method becomes part of the situation being studied.</p>
<p>The small sample further limits the reach of the findings. Eight younger U.S. creators at small companies or working independently cannot establish how the industry behaves. Two weeks gives a detailed view of particular project phases, but leaves open what happens during longer development cycles and after release. The study is most useful as a source of specific workflow questions a team can examine in its own practice.</p>
<h2>Real use supplied a more concrete design problem</h2>
<p>A designer working on a lawncare operations platform provided the clearest account of learning from people in their working environment. In one entry, the designer described reviewing voice-input instructions, recognizing that workers relied on dictation to log inventory, and making changes to the main screen with new guidance for first-time users.</p>
<p>The useful detail is the connection between a need and a design decision. The account raised a concrete question about whether people could readily find and understand support for the way they entered data. The study did not compare field visits with other research methods, and the same participant also reported a gap in tools or knowledge on another day.</p>
<p>For website teams applying this idea, observing situational difficulties such as glare or one-handed use should sit alongside research with people with disabilities. W3C’s <a href="https://www.w3.org/WAI/planning/involving-users/">guidance on involving users in web projects</a> recommends involving disabled people early and throughout development, while combining their input with accessibility standards. A field visit or a successful session with one person cannot cover the range of disabilities, assistive technologies, and ways of using a site.</p>
<h2>How to use the findings on a mobile website</h2>
<p>A reasonable practical application is to take one upcoming change—a newsletter signup, a checkout step, or an article’s navigation—and carry its accessibility decisions through the work already scheduled. The workflow below is an application to web projects, not an intervention evaluated in the app study.</p>
<h3>Make the early decision specific enough to build</h3>
<p>Consider a newsletter form. During sketching, a team can decide what each field asks for, where its label belongs, and how someone will recover from an error. Those decisions should travel with the design. The implemented page then needs its own verification: text drawn beside a field in a mockup does not establish the relationship in HTML.</p>
<p>W3C’s <a href="https://www.w3.org/WAI/tutorials/forms/labels/">form-labeling tutorial</a> explains that labels describe a control’s purpose and must be properly associated with it. For an explicit HTML association, a label’s <code>for</code> value matches the control’s <code>id</code>. That connection helps assistive technology present the correct label and gives users a larger clickable area. This is a useful example of carrying a design intention through to a verifiable implementation detail.</p>
<table>
<caption>Questions to carry through a mobile website change</caption>
<thead>
<tr>
<th scope="col">When</th>
<th scope="col">What to make explicit</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Sketching the task</th>
<td>Identify the reading order, control labels, and recovery path. Note questions that need input from users or accessibility expertise.</td>
</tr>
<tr>
<th scope="row">Reviewing the visual design</th>
<td>Record the actual color, spacing, and control decisions being checked. Keep their requirements and results with the relevant component.</td>
</tr>
<tr>
<th scope="row">Testing a coded prototype</th>
<td>Exercise the complete task with relevant input methods and assistive technology. Record the browser, device, test conditions, and barriers found.</td>
</tr>
<tr>
<th scope="row">Handing work to another person</th>
<td>Identify what has been decided, what has been verified, and what remains open. Assign someone to resolve each outstanding question.</td>
</tr>
</tbody>
</table>
<p>This approach fits W3C’s <a href="https://www.w3.org/WAI/planning-and-managing/implement/">guidance for implementing web accessibility</a>, which calls for specific responsibilities, evaluation as designs and components become available, and issues tracked through ordinary quality-assurance processes. A small team can keep this information in its existing tickets and component notes.</p>
<h3>Use reflection to uncover the next action</h3>
<p>The paper’s reflection finding suggests a modest experiment within an existing review or check-in. Ask questions that connect today’s decisions to tomorrow’s work:</p>
<ul>
<li>Which decision changed how someone can read, navigate, understand, or operate this feature?</li>
<li>What accessibility need does that decision address, and what evidence supports it?</li>
<li>What remains untested or unclear, and who will follow it up?</li>
</ul>
<p>These are suggested prompts, not a validated questionnaire. The study does not establish an ideal schedule or show that daily logging is necessary. Choose a cadence that fits the work, and judge its usefulness by whether it exposes decisions and unresolved questions that the team can act on.</p>
<p>The authors also warn that reflection cannot supply missing knowledge. If nobody understands how a screen reader encounters a form, a thoughtful check-in will still need to lead to learning, specialist help, or user research. Familiar design language can start the discussion; explicitly naming the accessibility requirement gives the team something to learn and verify.</p>
<h2>The proposed tools still need to prove their value</h2>
<p>The paper recommends tools that bring accessibility feedback into the design environment and match it to the work underway: hierarchy during wireframing, color during visual refinement, and clear records of components whose accessibility has not been verified. The authors place these ideas under the heading of Creative Accessibility Design Tools.</p>
<p>These are recommendations emerging from the research. This study did not evaluate a finished tool that delivers them or demonstrate that such a tool improves accessibility or productivity. Teams can use the ideas to assess their current workflow without assuming that a new plugin will resolve the underlying gaps.</p>
<p>Any tool result also needs an appropriate boundary. W3C’s <a href="https://www.w3.org/WAI/test-evaluate/">accessibility evaluation guidance</a> states that no tool alone can determine whether a site meets accessibility standards; knowledgeable human evaluation is required. Record what a check establishes and keep unresolved behavior visible.</p>
<p>At the next design review, put one real user task, its accessibility requirements, and the remaining verification work on the same page. Give the open work an owner. That makes the paper’s central observation useful in practice: accessibility decisions already being made can become decisions the team deliberately carries through to a working experience.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Safari 27.1: What Mobile Sites Should Test</title>
		<link>https://www.mobiletech.mobi/safari-27-1-iphone-duo-responsive-web/</link>
		
		<dc:creator/>
		<pubDate>Sat, 24 Oct 2026 16:03:20 +0000</pubDate>
				<category><![CDATA[Mobile UX]]></category>
		<guid isPermaLink="false">http://localhost:18082/?p=535</guid>

					<description><![CDATA[Safari 27.1 brings folding-screen previews to iPhone Duo. Here is what mobile sites should test now and which APIs remain behind flags.]]></description>
										<content:encoded><![CDATA[<p>Apple&#8217;s first foldable iPhone does not require a special version of your website. That is the useful takeaway from <a href="https://webkit.org/blog/18486/webkit-features-for-safari-27-1/">WebKit&#8217;s October 23, 2026 announcement of Safari 27.1</a>, released alongside iPhone Duo. The browser uses the same WebKit engine and web technologies as Safari on other iPhones. Regular responsive pages are meant to work without detecting a new device or adopting a new API.</p>
<p>There are two additions worth knowing about: Safari now changes its PDF presentation when the phone is folded like a book, and developers can experiment with APIs that describe the fold and the regions on either side of it. The catch is important: <strong>the Device Posture API and Viewport Segments API are developer previews behind feature flags, not capabilities ordinary Safari visitors can be expected to have enabled.</strong></p>
<p>For site owners, the immediate question is less exciting than building a dual-screen interface, but more consequential: does the page still work when a wide display becomes a narrow browser window, or when the browser&#8217;s available height suddenly changes?</p>
<h2>The screen gets bigger. The web viewport doesn&#8217;t always.</h2>
<p>Imagine someone opening an article on the outer display, unfolding the phone to read on the inner display, then placing two windows side by side. The device has not changed, but the amount of room available to each webpage has changed dramatically. This is the distinction responsive design already understands: <strong>a browser window, not a product name, determines how much space a page can use.</strong></p>
<p>WebKit&#8217;s published Safari 27.1 measurements put the outer display&#8217;s portrait viewport at <strong>382 CSS pixels wide</strong>. An inner-display portrait window is 669 CSS pixels wide, while a single inner-display landscape window reaches 951. Put two windows side by side on that inner landscape display, however, and <strong>each gets a viewport just 385 CSS pixels wide</strong>. The outer display in landscape offers 594 pixels of width, but far less height.</p>
<p>That means the same phone can present what looks to your CSS like a compact handset, a moderately wide reading window, or something approaching a tablet-sized canvas. More importantly, the 385-pixel split window is nearly as narrow as the 382-pixel outer display. A layout that assumes an unfolded phone must have room for a sidebar can fail precisely when the user starts multitasking.</p>
<p>These figures are CSS pixels, not the resolution of either physical panel. They describe the space Safari gives web content in the configurations WebKit documented. They are useful test points, not an invitation to write device-specific breakpoints or identify iPhone Duo from its user agent. The safer approach is the one WebKit recommends: fluid grids, flexible content, and <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/CSSOM_view/Viewport_concepts">layout decisions based on the viewport</a>.</p>
<h2>What to test on an existing mobile site</h2>
<p>Start with a real task, not a screenshot of your homepage. On a news site, open a story, expand an image, use search, and continue reading after a resize. On a store, open a product, select an option, and reach the checkout controls. A site can look acceptable at every static width while still breaking when a menu stays open, a chart retains its old dimensions, or a fixed button covers a form field.</p>
<ol>
<li><strong>Check the two narrow cases: 382 and 385 CSS pixels.</strong> Make sure navigation, article text, consent notices, tables, filters, and checkout controls remain usable without accidental horizontal scrolling. In a split window, don&#8217;t assume the site is on the outer display.</li>
<li><strong>Check the wider layouts: 669 and 951 CSS pixels.</strong> See whether your sidebar, cards, image gallery, or reading column actually benefits from the extra width. A two-column layout is only an improvement if both columns remain readable.</li>
<li><strong>Check short windows, not just narrow ones.</strong> The outer display in landscape has a small viewport height of 368 CSS pixels according to WebKit. Full-height menus, sticky buy buttons, video controls, and large consent dialogs can consume nearly all of that space.</li>
<li><strong>Change the window while a task is in progress.</strong> Open and close the device, rotate it, and enter or leave side-by-side mode. Watch for clipped content, displaced overlays, stale canvas dimensions, lost form input, or interfaces that need a reload before they fit.</li>
</ol>
<p>A basic responsive grid remains the right starting point. For example, a reading layout can keep its article in one column until there is enough room for a secondary rail:</p>
<pre><code>.story-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 1rem;
}

@media (min-width: 48rem) {
  .story-layout {
    grid-template-columns: minmax(0, 2fr) minmax(15rem, 1fr);
  }
}</code></pre>
<p>The 48-rem threshold here is an example of a content-driven breakpoint, not an iPhone Duo setting. If the rail needs more room, move the breakpoint; if it contributes little to the task, leave it below the story. The real test is whether the design still makes sense between the familiar phone and tablet sizes, including when text is enlarged.</p>
<p>You can run those checks without buying the hardware. WebKit points developers to <a href="https://developer.apple.com/videos/play/tech-talks/111461/">Device Hub in Xcode 27.1 or later</a>, which includes an iPhone Duo simulator. Open your site in the simulated Safari browser and switch among the device&#8217;s poses and window arrangements. Simulator checks are particularly useful for layout and API experiments; they do not replace real-device testing for touch behavior, performance, or other hardware-dependent details.</p>
<h2>The height problem deserves as much attention as the fold</h2>
<p>Safari&#8217;s published viewport chart includes both <code>SVH</code>, the smaller viewport height when browser controls take up more room, and <code>LVH</code>, the larger height available when those controls retract. On the outer display in portrait, those figures are 580 and 618 CSS pixels. In inner-display landscape, they are 585 and 620. This is normal mobile-browser behavior, but it becomes easier to miss when testing a new device only at its largest size.</p>
<p>Consider a menu designed as a full-screen panel with a permanently visible confirmation button at the bottom. If it is sized as though the browser controls were already hidden, the button may sit outside the space the user can actually see. A full-page layout also needs to cope with the software keyboard, which can further reduce the visible area.</p>
<p>For sections that should fill the available page without hiding their own content, a flexible <code>min-height: 100svh</code> is often safer than a rigid <code>height: 100vh</code>. The <code>dvh</code> unit can be useful when a surface must track the changing visible viewport, though it can also cause resizing as browser controls move. <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/length">MDN&#8217;s explanation of small, large, and dynamic viewport units</a> makes the trade-off clear. Whatever unit you choose, keep critical controls reachable and allow content to scroll when space is tight.</p>
<h2>The fold-aware APIs are for prototypes, not required site updates</h2>
<p>The experimental APIs answer a different question from responsive CSS. Ordinary media queries can tell a page that it is 385 pixels wide. They cannot tell it whether one browser viewport is being divided by a physical fold, or where that division lies. That distinction matters for an interface intentionally designed to put a page of text on one side of a book-like device and supporting tools on the other.</p>
<h3>Device Posture: is the screen partially folded?</h3>
<p>With the preview enabled, the <a href="https://www.w3.org/TR/device-posture/">Device Posture API</a> offers a CSS <code>device-posture</code> media query and a JavaScript <code>navigator.devicePosture</code> interface. Safari reports <code>folded</code> for partial folds such as book or laptop poses, and <code>continuous</code> when the screen is flat. Crucially, <code>continuous</code> covers both a fully opened and a fully closed display. It does not mean “large screen.”</p>
<p>Posture alone also does not say whether the halves sit left and right or above and below. A reader interface that reacts only to <code>folded</code> could put its controls in the wrong place when the user rotates the device. For precise placement, the page needs information about the available segments as well.</p>
<h3>Viewport Segments: where are the usable regions?</h3>
<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/API/Viewport_segments_API">Viewport Segments API</a> describes the distinct regions within a single viewport separated by a fold or hinge. CSS can query <code>horizontal-viewport-segments: 2</code> for two regions side by side, or <code>vertical-viewport-segments: 2</code> for two stacked regions. Indexed <code>env()</code> values, such as <code>env(viewport-segment-width 0 0)</code> and <code>env(viewport-segment-width 1 0)</code>, expose their dimensions; JavaScript can inspect <code>window.viewport.segments</code>.</p>
<p>The distinction between segments and windows is easy to miss. Two side-by-side browser windows on the inner display have two separate narrow viewports. That does <em>not</em> make them the two segments of one webpage. Segment-aware layout is for a single web experience interacting with a fold, not a way to spread your site across someone else&#8217;s window.</p>
<p>As of Safari 27.1 on iPhone Duo, developers must explicitly enable both previews under <strong>Settings &gt; Apps &gt; Safari &gt; Advanced &gt; Feature Flags</strong>, selecting “Device Posture API” and “Viewport Segments API.” WebKit says their behavior may still evolve. MDN also marks the segment APIs as experimental and not available across all widely used browsers. A production site must therefore remain complete and usable when these features are absent.</p>
<p>There is still value in experimenting. A reading app might place text in one region and annotations in the other; a learning interface might put a demonstration above controls in laptop pose. Build either as an optional enhancement to a working one-column or flexible layout. If the API is unavailable, the user should not lose the article, the video, the form, or the primary action.</p>
<h2>Why Safari won&#8217;t yet let webpages fill the strip behind the outer controls</h2>
<p>There is another iPhone Duo detail that site developers cannot fix with a fold media query. On the outer display, Safari moves controls to the side to preserve vertical room. In Safari 27.1, webpage content does <strong>not</strong> extend into the strip behind those controls. Safari instead extends the webpage&#8217;s background color into that space.</p>
<p>That can make a page appear to have an unusually large empty margin, even when its content fits the actual viewport correctly. It is not evidence that the site needs a wider CSS canvas. Nor is adding <code>viewport-fit=cover</code> a documented way to claim that strip today.</p>
<p>Safari has long supported <code>viewport-fit=cover</code> and <code>env(safe-area-inset-*)</code> for designing around display edges, as WebKit explained in its <a href="https://webkit.org/blog/7929/designing-websites-for-iphone-x/">iPhone X safe-area guidance</a>. But the new outer-control region is a separate design question. WebKit says it is considering how to allow content farther into that area without encouraging sites to hide text or buttons beneath controls. Its concern is practical: existing edge-to-edge layouts often fail to account correctly for insets on all four sides.</p>
<p>For now, check that backgrounds look intentional, that important content stays within the usable viewport, and that any existing safe-area padding works on all edges. Do not redesign the page around an extension API Apple has not shipped or promise stakeholders a full-width outer-display layout that Safari does not currently offer.</p>
<h2>PDFs get the immediate, user-visible improvement</h2>
<p>The clearest change that Safari 27.1 actually gives users by default is in PDF reading. When an iPhone Duo is partly folded like a book, Safari shows a PDF as <strong>two pages side by side</strong>. Open the device flat or close it fully, and the PDF returns to a single-page presentation. That behavior comes from Safari, not from a new CSS rule that a publisher needs to implement.</p>
<p>If your publication offers reports, instruction manuals, catalogs, or downloadable documents as ordinary PDF links, this is a reason to check how a representative document reads in Safari&#8217;s built-in presentation, not a reason to create a custom two-page viewer. If your site already renders PDF pages inside its own JavaScript or canvas-based interface, do not assume that interface automatically acquires Safari&#8217;s behavior; it has its own layout to test.</p>
<p>The benefit is straightforward for readers: a document originally designed around facing pages can now appear that way when the phone is held like a book. A browser feature can improve the reading experience without requiring every publishing platform to reinvent document navigation.</p>
<h2>What should change now—and what can wait?</h2>
<p><strong>Change now:</strong> repair layouts that depend on a particular phone width, assume an open foldable always has a wide viewport, or let fixed panels cover content in a short window. Test the actual browsing journey at the documented narrow, wide, and short sizes. Make sure layout changes do not discard user input or render critical controls unreachable. These are ordinary responsive-design issues, but the new display combinations make them easier to expose.</p>
<p><strong>Prototype, if your product needs it:</strong> posture-aware reading, teaching, media, or productivity layouts that genuinely benefit from separating two regions around a fold. Use Safari&#8217;s feature flags and the iPhone Duo simulator to learn how the APIs behave. Keep the nonexperimental layout as the default and avoid making the new APIs essential to reading, buying, or completing a task.</p>
<p><strong>Wait:</strong> for the fold-aware APIs to become normal Safari capabilities before treating them as a broadly available interface contract, and for Apple to define a safe way to use the outer-display space behind Safari&#8217;s side controls. Nothing in the October 23 announcement establishes a new mobile SEO ranking requirement, device-specific URL strategy, or reason to replace a responsive website with a separate iPhone Duo edition.</p>
<p>Safari 27.1&#8217;s most useful lesson is not that every site should imitate a book. It is that the difference between a phone and a tablet is increasingly the wrong question. The right one is whether a reader can keep using your site as the space available to it changes—and, for now, that is something the web&#8217;s established layout tools can already handle.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Chrome DevTools Finds Memory Mobile Sites Keep</title>
		<link>https://www.mobiletech.mobi/chrome-devtools-dead-context-fields-mobile-memory/</link>
		
		<dc:creator/>
		<pubDate>Fri, 23 Oct 2026 11:28:19 +0000</pubDate>
				<category><![CDATA[Mobile Web]]></category>
		<guid isPermaLink="false">http://localhost:18082/?p=530</guid>

					<description><![CDATA[Chrome DevTools can reveal unused variables retained by closures. Learn how to investigate memory growth during long mobile web sessions.]]></description>
										<content:encoded><![CDATA[<p>A mobile website can feel fast when it opens and still become sluggish after a long visit. A reader moves between articles, a shopper browses dozens of products, or a user leaves a web app open while switching between other apps. JavaScript finishes one task, but some of the data it created remains in memory. Until now, finding one particular reason for that behavior could require painstaking inspection of Chrome&#8217;s heap snapshots.</p>
<p>Chrome DevTools has a new way to expose it. In its <a href="https://developer.chrome.com/blog/new-in-devtools-november-2026">October 20, 2026 announcement</a>, Google introduced detection of <strong>dead context fields</strong>: variables that are no longer accessed by any live closure but remain in memory because another closure keeps their shared JavaScript execution context alive.</p>
<p>For developers of mobile sites, PWAs, and long-running web applications, the practical benefit is a shorter path from noticing excessive memory use to identifying the reference that prevents memory from being reclaimed. The feature does not automatically fix a leak or change how websites run. It makes a previously difficult retention pattern easier to see.</p>
<p>The release timing matters. Google&#8217;s announcement is titled &#8220;New in DevTools – November 2026,&#8221; but it was published on October 20 and covers improvements associated with Chrome 155 and 156. <a href="https://chromereleases.googleblog.com/2026/10/stable-channel-update-for-desktop_02018666648.html">Chrome 156 entered the desktop Stable channel on October 20</a>, with availability rolling out over subsequent days and weeks. This is an announced debugging capability in the current release cycle, not a proposed browser feature waiting for November.</p>
<h2>How a finished task can keep using memory</h2>
<p>JavaScript closures are useful because they let a function remember variables from the scope where it was created. A button handler remembers its component state. A network callback remembers a request&#8217;s data. A reconnect handler remembers whether synchronization is enabled.</p>
<p>The complication lies in how V8, Chrome&#8217;s JavaScript engine, represents some captured variables. Variables from the same enclosing lexical scope can be stored together in an internal object called a <code>Context</code>. Multiple closures may share that context.</p>
<p>If one closure remains reachable, the shared context may remain reachable too, including references to objects that the surviving closure never reads.</p>
<p>Consider this simplified JavaScript example:</p>
<pre><code>function startSession() {
  const largeBuffer = new Uint8Array(10 * 1024 * 1024);
  let syncEnabled = true;

  Promise.resolve().then(() =&gt; {
    processData(largeBuffer);
  });

  window.addEventListener('online', function onOnline() {
    if (syncEnabled) {
      resumeSync();
    }
  });
}</code></pre>
<p>The promise callback uses <code>largeBuffer</code> for a one-time operation. Once that callback finishes and is no longer retained, nothing in the surviving event handler needs the buffer.</p>
<p>But <code>onOnline</code> continues to exist as a window event listener. It accesses <code>syncEnabled</code>, a variable from the same enclosing scope. If V8 stores the captured variables together, the listener can keep the shared context alive, including its reference to <code>largeBuffer</code>.</p>
<p>The buffer is logically finished with its job, but garbage collection operates on reachability. A live reference from the context still leads to the buffer. The garbage collector cannot simply assume the application&#8217;s intentions and discard it.</p>
<figure>
<svg id="closure-retention-diagram-2026" xmlns="http://www.w3.org/2000/svg" width="100%" viewbox="0 0 480 560" role="img" aria-labelledby="closure-retention-title-2026 closure-retention-desc-2026">
<title id="closure-retention-title-2026">How a live closure retains an unused buffer</title>
<desc id="closure-retention-desc-2026">A window event listener keeps the onOnline closure alive. That closure retains a shared V8 context containing both an active syncEnabled variable and a dead largeBuffer variable. The latter still points to a large buffer, preventing its collection.</desc>
<style>
#closure-retention-diagram-2026 text {font-family:Arial,sans-serif;fill:#182b43;font-size:20px}
#closure-retention-diagram-2026 .heading {font-weight:700}
#closure-retention-diagram-2026 .secondary {font-size:17px}
#closure-retention-diagram-2026 .arrow {stroke:#345779;stroke-width:3;stroke-linecap:round}
</style>
<rect x="25" y="12" width="430" height="80" rx="12" fill="#e9f0fc" stroke="#345779" stroke-width="2"></rect>
<text x="240" y="44" class="heading" text-anchor="middle">Window event listener</text>
<text x="240" y="71" class="secondary" text-anchor="middle">Keeps a callback registered</text>
<line x1="240" y1="93" x2="240" y2="133" class="arrow"></line>
<polygon points="233,123 247,123 240,135" fill="#345779"></polygon>
<rect x="25" y="138" width="430" height="83" rx="12" fill="#e9f0fc" stroke="#345779" stroke-width="2"></rect>
<text x="240" y="172" class="heading" text-anchor="middle">Live closure: onOnline()</text>
<text x="240" y="200" class="secondary" text-anchor="middle">Reads syncEnabled, not largeBuffer</text>
<line x1="240" y1="222" x2="240" y2="256" class="arrow"></line>
<polygon points="233,247 247,247 240,259" fill="#345779"></polygon>
<rect x="25" y="263" width="430" height="186" rx="12" fill="#f4f6f9" stroke="#345779" stroke-width="2"></rect>
<text x="240" y="295" class="heading" text-anchor="middle">Shared V8 Context</text>
<rect x="43" y="312" width="394" height="53" rx="8" fill="#e0f2e8" stroke="#26734f" stroke-width="2"></rect>
<text x="57" y="333" class="secondary heading">LIVE FIELD</text>
<text x="57" y="354" class="secondary">syncEnabled: still accessed</text>
<rect x="43" y="377" width="394" height="54" rx="8" fill="#fff0e6" stroke="#ae5435" stroke-width="2" stroke-dasharray="6 4"></rect>
<text x="57" y="399" class="secondary heading">DEAD FIELD</text>
<text x="57" y="420" class="secondary">largeBuffer: no live reader</text>
<line x1="240" y1="450" x2="240" y2="491" class="arrow"></line>
<polygon points="233,482 247,482 240,494" fill="#345779"></polygon>
<rect x="25" y="498" width="430" height="53" rx="10" fill="#fff0e6" stroke="#ae5435" stroke-width="2"></rect>
<text x="240" y="531" class="secondary heading" text-anchor="middle">Unused buffer remains reachable</text>
</svg>
<figcaption>Conceptual reference path, not to scale. The live event handler retains a shared context, which can retain data no live closure reads. The mechanism is described in <a href="https://developer.chrome.com/blog/new-in-devtools-november-2026">Chrome's dead context field announcement</a>.</figcaption>
</figure>
<p>This is not a claim that every closure holds on to every variable declared alongside it. Engines optimize scopes, and the actual memory layout depends on the code and runtime behavior. The problem occurs when a retained context contains references whose useful lifetime has already ended.</p>
<p>It is particularly awkward to investigate because removing the short-lived callback does not necessarily remove the reference held inside the shared context. The developer can see that the job finished, and yet the object remains reachable.</p>
<h2>What Chrome DevTools now reveals</h2>
<p>Chrome already had tools for examining heap snapshots, including the objects retained by a variable, the references keeping them alive, and the memory that could potentially be recovered by releasing them.</p>
<p>Its <a href="https://developer.chrome.com/blog/new-in-devtools-october-2026">previous DevTools roundup, published September 22</a>, added capabilities for inspecting memory retained by V8 execution contexts, alongside more flexible heap-snapshot filtering and querying. Those improvements helped developers locate memory associated with contexts.</p>
<p>The new addition goes a step further. Instead of merely finding memory retained through a context, DevTools analyzes whether the corresponding context field is still needed by any live closure.</p>
<p>According to Google&#8217;s announcement, V8 includes lexical scope metadata in heap snapshots. DevTools uses that information to associate closures with their shared <code>Context</code> objects and identify variables for which no remaining live closure has an access. It then highlights dead fields and prioritizes findings by memory impact.</p>
<p>That distinction matters. A context retaining a large object is not automatically a bug: the application might still need it. A context retaining an object that no live closure accesses is a more specific diagnostic clue.</p>
<p>The new route through the interface is the <strong>Objects retained by contexts</strong> filter in the Memory panel&#8217;s heap snapshot Summary view. Google&#8217;s accompanying example shows an <code>ArrayBuffer</code> retained through a context and a network-status event handler. The Retainers section exposes the relationship between the buffered data, its context field, and the closure that remains registered.</p>
<p>There are two numbers worth keeping separate when evaluating a finding. <strong>Shallow size</strong> describes memory attributed directly to an object. <strong>Retained size</strong> concerns memory that could become unreachable if the relevant retaining object and its dependent references were removed. As <a href="https://developer.chrome.com/docs/devtools/memory-problems/get-started">Chrome&#8217;s memory terminology documentation</a> explains, a small object can indirectly keep much more memory alive.</p>
<p>That is why a seemingly insignificant callback can be worth investigating. Its own memory footprint may be tiny while the reference path behind it leads to a large data structure.</p>
<h2>Why long mobile sessions make this worth checking</h2>
<p>A conventional page-load test captures only a short period of a site&#8217;s behavior. It can reveal expensive scripts, network delays, and rendering work, but it does not necessarily show what happens when the same JavaScript environment remains active through repeated interactions.</p>
<p>Consider a shopping site that behaves like a single-page application. A customer opens product details, changes filters, browses categories, revisits the cart, and continues shopping without a full document reload. Several screens may be gone from view, but their network callbacks, state objects, or event handlers can still exist.</p>
<p>A publisher&#8217;s reading experience presents a similar opportunity for retention. An article viewer may prefetch content, assemble recommendation data, and register listeners for connectivity or user activity. If its cleanup only removes visible elements, references inside JavaScript contexts can survive the transition to the next article.</p>
<p>The same pattern can occur in mobile dashboards, editors, maps, messaging interfaces, and installed PWAs. These products routinely maintain a document for far longer than an ordinary navigation-based page. Even a modest amount of unnecessarily retained data can become important if each new workflow creates another instance.</p>
<p>The consequences are not limited to a bigger number in a profiler. Google&#8217;s <a href="https://developer.chrome.com/docs/devtools/memory-problems/">memory troubleshooting guide</a> identifies progressively worsening performance and frequent garbage collections as symptoms worth investigating. Garbage collection work can interrupt JavaScript execution, and excessive memory use can become more serious on devices with less available RAM.</p>
<p>However, a dead field is not proof that users are already experiencing a performance problem. A single retained object may have little practical impact. The concern grows when retention repeats, the objects are large, or the application is used for extended periods on memory-constrained devices.</p>
<p>A useful distinction for mobile teams is therefore between <em>memory allocated</em> and <em>memory unnecessarily retained</em>. A page that briefly needs a large data structure is not inherently unhealthy. A page that keeps that structure after its job ends may be carrying a cost into every subsequent interaction.</p>
<h2>How to investigate a long-running mobile session</h2>
<p>The new filter is most valuable when it is used to answer a specific question: after a user completes an operation, does the page release the data that operation needed?</p>
<p>Chrome&#8217;s <a href="https://developer.chrome.com/docs/devtools/memory-problems/heap-snapshots">heap snapshot documentation</a> provides the underlying workflow for recording, comparing, and inspecting memory. With dead context field detection, a practical investigation looks like this:</p>
<ol>
<li><strong>Reproduce an extended user journey.</strong> Start with a representative session, such as opening and closing a product detail panel, switching between routes, loading more articles, or repeatedly entering and leaving an editor. Keep the document alive if that matches the product&#8217;s real behavior.</li>
<li><strong>Take a baseline heap snapshot.</strong> Open DevTools, select the Memory panel, choose Heap snapshot, select the relevant JavaScript VM instance, and take the snapshot. Chrome performs garbage collection as part of snapshot capture, making the resulting object graph more useful than an arbitrary reading taken between collections.</li>
<li><strong>Repeat the operation and reverse it.</strong> Open and close the panel several times, or navigate between the same screens. Allow pending work to finish. Take another snapshot without reloading the page.</li>
<li><strong>Filter for retained contexts.</strong> In the snapshot&#8217;s Summary view, choose Objects retained by contexts. Inspect substantial findings, expand the relevant objects, and follow the Retainers path. Look for a heavy object reached through a context field associated with a surviving callback or handler.</li>
<li><strong>Compare the before-and-after snapshots.</strong> Use Comparison view to see which objects remain after the operation should have released them. A growing collection of retained objects across repeated cycles deserves closer examination than an isolated allocation.</li>
<li><strong>Verify the cause after a code change.</strong> Remove or shorten the unwanted reference, repeat the same interaction sequence, and check whether the relevant retained objects disappear or stop accumulating.</li>
</ol>
<p>Interpret the results carefully. A snapshot is a point-in-time picture of reachable objects, not a continuous record of everything the application has done. A large retained object may also have another independent reference, so addressing one dead context field might not free the entire reported amount.</p>
<p>DevTools can influence what remains reachable as well. Google&#8217;s <a href="https://developer.chrome.com/docs/devtools/memory-problems/get-started">memory terminology guide</a> notes that debugger activity and Console evaluations can retain objects. Clear unnecessary Console references and avoid leaving breakpoints active when trying to confirm that an object has been released.</p>
<p>For a mobile-specific investigation, use a real device where possible. Chrome supports <a href="https://developer.chrome.com/docs/devtools/remote-debugging/">remote debugging of Android devices</a> from a desktop computer. After enabling USB debugging and connecting the phone, a developer can inspect the actual browser tab rather than relying solely on a desktop viewport emulating a phone.</p>
<p>There is one important version detail: Chrome&#8217;s remote-debugging documentation says the browser version on the Android device determines the DevTools instance opened for that device. Updating desktop Chrome does not guarantee that every remotely inspected phone will expose the newest interface. If the new filter is missing, check the version of the browser being inspected.</p>
<h2>Fixing the reference rather than masking the memory use</h2>
<p>Once the Retainers path identifies the surviving closure, the next decision is about ownership and lifetime. Which part of the application actually needs to keep running, and which data should have disappeared when the operation ended?</p>
<p>In the earlier example, the connectivity handler and the one-time buffer-processing task serve different purposes. They do not need to share ownership of a large buffer. Moving the expensive data work into a separate, short-lived lexical scope can prevent the long-lived listener from retaining that data through the same context.</p>
<p>Another option is to release an explicit reference after its last required use. Where the logic needs a mutable reference, setting it to <code>null</code> after completion can break the retaining path, provided no other live reference still points to the object. That does not force garbage collection immediately; it makes the object eligible once it is otherwise unreachable.</p>
<p>Event listener cleanup matters for the same reason. A handler attached to <code>window</code> can remain registered after the component that created it has disappeared. Removing the listener when its owner is disposed can release the closure, its context, and potentially other objects reachable only through that chain.</p>
<p>For components with several lifecycle stages, the better design is often a clear ownership boundary: route-specific data belongs to the route, temporary processing buffers belong to the processing operation, and persistent listeners retain only state they genuinely need.</p>
<p>None of these changes should be made blindly. A cache may be intentionally retained because recreating it is expensive. A callback may need its data after a delayed response. Releasing something too early can cause correctness problems or additional network and CPU work.</p>
<p>The useful result from the new DevTools analysis is not simply a list of objects to delete. It is evidence that helps a developer decide whether the continued lifetime of a particular object is intentional.</p>
<h2>What the new detector cannot tell you</h2>
<p>Dead context field detection addresses one mechanism of JavaScript memory retention. It does not turn heap snapshots into a complete explanation of a page&#8217;s memory footprint.</p>
<p>Objects may remain alive through global caches, maps, arrays, detached DOM trees, subscriptions, pending asynchronous work, or references held by other objects. Some of those problems can involve closures, but they do not all depend on dead context fields.</p>
<p>Other memory also exists outside the objects most easily described by a JavaScript heap snapshot. Images, native browser allocations, graphics resources, and related process memory can affect the experience without appearing as a simple unused closure variable. Chrome&#8217;s memory documentation distinguishes the JavaScript heap from the broader renderer memory footprint for this reason.</p>
<p>Nor should a highlighted dead field be interpreted as an exact promise of recoverable device RAM. The diagnostic identifies a reference pattern and prioritizes it by memory impact. The amount actually reclaimed depends on the full reference graph and the runtime&#8217;s memory management.</p>
<p>Conversely, an empty result is not proof that an application has no memory problems. The analysis depends on available V8 scope information and the live object graph captured at the moment of the snapshot. Other retention mechanisms and behaviors outside the observed session still require investigation.</p>
<p>Finally, this is a Chrome DevTools debugging improvement, not a new JavaScript API, cross-browser performance guarantee, or automatic change to garbage collection. Websites do not become more memory-efficient merely because their developers or visitors update Chrome. A diagnosed issue still needs an appropriate application-level fix.</p>
<h2>The bigger lesson for mobile web performance</h2>
<p>Mobile performance work often starts with the first few seconds of a visit: loading the main content, responding to the first interaction, and avoiding visible layout movement. Those things remain important, but they describe only part of the experience of a persistent web application.</p>
<p>A session that lasts 30 minutes places different demands on a site. Workflows begin and end. Data arrives and becomes obsolete. Listeners are registered and, ideally, unregistered. A long-lived page needs to keep useful state without accumulating references to everything it has previously handled.</p>
<p>Chrome&#8217;s new dead context field detection gives developers a more direct way to investigate one of the less obvious failures in that process. Instead of searching through a large object graph and guessing which closures still need a shared variable, they can use V8&#8217;s scope information to identify fields that remain reachable despite no longer being accessed.</p>
<p>For a mobile site that users open briefly, this may be an occasional diagnostic convenience. For a shopping interface, publisher reader, dashboard, editor, or PWA expected to remain open through a long session, it is a useful addition to regular memory testing.</p>
<p>The most important question is no longer just how much memory the page uses when it loads. It is <strong>how much memory the page continues to own after the user is finished with the work that created it</strong>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Chrome 156 and SQLite: Check Your Offline Data</title>
		<link>https://www.mobiletech.mobi/chrome-156-indexeddb-sqlite-offline-data/</link>
		
		<dc:creator/>
		<pubDate>Thu, 22 Oct 2026 10:19:12 +0000</pubDate>
				<category><![CDATA[Mobile Web]]></category>
		<guid isPermaLink="false">http://localhost:18082/?p=527</guid>

					<description><![CDATA[Chrome 156 stages SQLite for new IndexedDB stores. Check existing data, transaction timing, attachments, and storage use before changing your mobile app.]]></description>
										<content:encoded><![CDATA[<p>Chrome’s shift to SQLite-backed IndexedDB is expanding beyond Incognito. The <a href="https://chromestatus.com/release-notes/156">stage listed for Chrome 156</a> covers newly created on-disk storage, while existing LevelDB data stays in place. For mobile sites that save drafts, attachments, and work waiting to sync, the immediate consequence is a transition in which users can have different storage backends on the same browser version.</p>
<p>Google announced <a href="https://chromereleases.googleblog.com/2026/10/chrome-for-android-update_095004068.html">Chrome 156.0.8078.47 for Android on October 20, 2026</a>, with availability on Google Play over the following days. The SQLite change deserves a separate check from that browser update. Receiving Chrome 156 does not establish which storage engine a particular site is using.</p>
<p>The application still uses IndexedDB: databases, object stores, indexes, and transactions. Chromium’s <a href="https://groups.google.com/a/chromium.org/g/blink-dev/c/jS0khnC5IWA/m/JfLnx8U-BgAJ">announcement of the backend change</a> explicitly preserves the web API. Sites gain no SQL interface, and an existing IndexedDB schema does not need a version bump simply because the browser changes its implementation. The useful work is checking what happens to saved data and transaction timing.</p>
<h2>What is actually rolling out?</h2>
<p>As of October 22, the <a href="https://chromestatus.com/api/v0/features/5161589557821440">ChromeStatus feature record</a> assigns milestone 156 to desktop, Android, and Android WebView, but still labels the implementation status “Proposed.” Those fields describe an intended release milestone without establishing deployment to every installation. The entry supplies no iOS milestone; the Android announcement should not be read as an iPhone storage announcement.</p>
<p>The <a href="https://chromium.googlesource.com/chromium/src/+/refs/tags/156.0.8078.47/content/public/common/content_features.cc">released 156.0.8078.47 source</a> leaves the on-disk rollout feature disabled by default. Field-trial settings can override that default. In its <a href="https://github.com/MicrosoftEdge/WebView2Feedback/discussions/5754">October 8 validation announcement</a>, Microsoft specifically says Chrome and Edge are experimenting with the change through field trials. Taken together, these sources establish a staged transition; they do not establish a universal activation date or percentage. For a site team, that makes the browser version an insufficient way to classify storage behavior.</p>
<p>This is the second stage of the project. The earlier <a href="https://developer.chrome.com/release-notes/145">Chrome 145 release notes</a> covered SQLite for in-memory contexts, including Incognito. The stage listed for Chrome 156 extends the work to new on-disk stores. Migration of existing LevelDB data remains a subsequent stage in the published rollout description. An Incognito test therefore cannot substitute for testing an established user’s on-disk data.</p>
<h2>Existing data stays on its current backend</h2>
<p>The important boundary is the browser’s storage bucket for the site, rather than the age of the entire browser profile. The <a href="https://chromium.googlesource.com/chromium/src/+/refs/tags/156.0.8078.47/content/browser/indexed_db/instance/bucket_context.cc">backend-selection code in Chrome 156</a> checks whether a LevelDB store already exists for that bucket. In the new-stores stage, SQLite is selected when it does not.</p>
<p>The practical consequences of that selection rule are:</p>
<ul>
<li><strong>A fresh test profile can create SQLite storage</strong> when the new-stores rollout stage is enabled.</li>
<li><strong>An established profile can also create SQLite storage</strong> for a site whose bucket has no existing LevelDB store.</li>
<li><strong>A returning user’s existing LevelDB store stays on LevelDB</strong> during this stage. Adding another named IndexedDB database, or another object store inside one, does not by itself change the bucket’s backend.</li>
</ul>
<p>This distinction matters when reproducing a support report. A developer opening the site in a clean profile may be exercising a different storage path from the person reporting the problem. Preserve an established test profile alongside a clean one. Keep working user data intact: clearing site storage to force a transition can erase the very drafts and queued uploads an offline application exists to protect.</p>
<h2>A background import can compete with a draft save</h2>
<p>The most visible execution change concerns <code>readwrite</code> transactions. With SQLite, only one can run at a time within a given IndexedDB database, even when the transactions address different object stores. Chromium’s LevelDB implementation could overlap writes when their scopes—the sets of object stores they access—did not intersect. The new implementation still permits eligible <code>readonly</code> transactions to run alongside a write when their scopes do not overlap.</p>
<figure>
<svg xmlns="http://www.w3.org/2000/svg" id="mt-idb-scheduling-20261022" viewbox="0 0 360 350" width="100%" role="img" aria-labelledby="mt-idb-scheduling-title-20261022 mt-idb-scheduling-desc-20261022">
<title id="mt-idb-scheduling-title-20261022">Write concurrency within one IndexedDB database</title>
<desc id="mt-idb-scheduling-desc-20261022">Two readwrite transactions access separate object stores in the same database. The LevelDB example allows their execution to overlap. The SQLite example runs them sequentially. This schematic shows one possible order, with no measured durations.</desc>
<rect x="1" y="1" width="358" height="348" rx="12" fill="#ffffff" stroke="#cbd5e1"></rect>
<g font-family="Arial, sans-serif" fill="#172033">
<text x="20" y="28" font-size="17" font-weight="700">One database, two object stores</text>
<text x="20" y="51" font-size="14">A: draft save · B: background import</text>
<text x="20" y="88" font-size="16" font-weight="700">LevelDB: overlap was possible</text>
<rect x="20" y="103" width="190" height="32" rx="5" fill="#f0f4f8" stroke="#526477" stroke-width="2"></rect>
<text x="31" y="125" font-size="15">Write B</text>
<rect x="110" y="145" width="100" height="32" rx="5" fill="#183b56"></rect>
<text x="121" y="167" font-size="15" fill="#ffffff">Write A</text>
<line x1="20" y1="195" x2="338" y2="195" stroke="#cbd5e1"></line>
<text x="20" y="224" font-size="16" font-weight="700">SQLite: one writer at a time</text>
<rect x="20" y="239" width="190" height="32" rx="5" fill="#f0f4f8" stroke="#526477" stroke-width="2"></rect>
<text x="31" y="261" font-size="15">Write B</text>
<rect x="230" y="281" width="100" height="32" rx="5" fill="#183b56"></rect>
<text x="241" y="303" font-size="15" fill="#ffffff">Write A</text>
<text x="20" y="337" font-size="13">Time moves left to right; not to scale.</text>
</g>
</svg>
<figcaption>Conceptual execution timelines, not performance measurements. The SQLite example illustrates one possible order, not an ordering guarantee. The <a href="https://chromium.googlesource.com/chromium/src/+/refs/tags/156.0.8078.47/content/browser/indexed_db/instance/sqlite/database_connection.h">Chrome 156 transaction implementation</a> documents the single-writer rule and the separate treatment of read-only transactions.</figcaption>
</figure>
<p>Consider a field-service PWA importing a large catalog while someone saves a job note. If those operations write to separate object stores in the same database, the note’s transaction may have to wait for the import. That is an implication of the scheduling change, not a measured slowdown for every application. The relevant test is the time the user waits for the save confirmation while background work is active.</p>
<p>The <a href="https://w3c.github.io/IndexedDB/#transaction-scheduling">IndexedDB scheduling rules</a> already allow browsers to serialize non-overlapping write transactions. Neither implementation creates a general promise about the order in which independent transactions finish. Code with a dependency should express it explicitly, and operations that only read should use <code>readonly</code>.</p>
<p>Keep transactions limited to work that needs to commit together, and measure bulk-write batches against foreground saves. Splitting a database solely to recover parallel writes introduces another tradeoff: as the <a href="https://docs.google.com/document/d/1-N4NqdYjqaWEbfVPYnCg2Ho1u4YK48pUoQ_tSy9SUg8/edit?usp=sharing">implementation design</a> explains, separate databases cannot share one atomic IndexedDB transaction. A draft and its upload-queue entry may need that shared commit more than they need additional write concurrency.</p>
<h2>Separate databases need coordinated recovery</h2>
<p>The architecture changes the possible reach of a storage failure. LevelDB grouped the IndexedDB databases within a storage bucket in one backing store; the SQLite design gives each named database its own SQLite database. The developers’ <a href="https://docs.google.com/document/d/1S9Rx67BmCZIP7tpasKaQ2FlV4TL1Z15DitR2kRZs9nw/edit?usp=sharing">public technical PSA</a> says unrecoverable corruption or an I/O error may consequently affect one database instead of the whole IndexedDB storage area. This is an expected improvement in isolation, with a qualification for applications that distribute related state across databases.</p>
<p>Suppose one database lists articles available offline and another holds their content. If the list survives a failure affecting the content database, the app can display a download that it can no longer open. Recovery logic should check the relationship between those records, distinguish downloadable content from unsynced user work, and explain incomplete offline availability accurately. Successfully opening one database is not a health check for all of them.</p>
<h3>Check the saved attachment itself</h3>
<p>Blob handling is another reason for the rewrite. The older implementation kept Blob metadata in LevelDB and the bytes in separate files. The <a href="https://docs.google.com/document/d/1-ddLfytabspG4sRCQwFb5TULw9LjlW4DLgunEuvonHI/edit?usp=sharing">Blob design document</a> explains the difficulty of keeping those stores consistent; the new backend writes new Blob contents into SQLite. The PSA’s authors expect fewer cases in which reading a Blob fails independently of the rest of the database. That remains an expectation, rather than a guarantee that attachments cannot be lost.</p>
<p>For a mobile publishing tool or inspection app, include actual photos, PDFs, or audio in the test data. After the transaction completes, close and reopen the application, retrieve each attachment, and read or decode its contents. A filename in an offline list does not establish that the underlying file can still be used.</p>
<h2>Disk use can grow even when writes improve</h2>
<p>In their technical PSA, the developers report roughly twice-as-fast writes in benchmarks, while identifying reliability and maintainability as the main goals. That result is not a forecast for a particular phone or workload. A faster individual storage operation can coexist with longer waits between competing transactions.</p>
<p>Storage size also depends on the shape of the data. The <a href="https://docs.google.com/document/d/1rRYb_qSsYgNK_HvMUK0f-gIZn1sqy2w8PkN9YFN8PSw/edit?tab=t.0#heading=h.ov4gcfqrgju0">compression design</a> describes compressing record values rather than obtaining LevelDB’s block-level compression. Keys and metadata remain uncompressed. The authors expect that some larger values can compress better, while smaller values can fare worse. That potential improvement should not be generalized to stored media files.</p>
<p>Indexes add another cost. The <a href="https://chromium.googlesource.com/chromium/src/+/refs/tags/156.0.8078.47/content/browser/indexed_db/instance/sqlite/database_connection.cc">released SQLite implementation</a> duplicates record keys in index entries to speed cursor iteration. Long primary keys and numerous indexes can therefore make a meaningful difference to disk consumption. A test populated with short integer keys and a single index will miss the storage characteristics of a production catalog built around long compound keys.</p>
<p>Use representative key lengths, index counts, record sizes, and attachments when comparing profiles. Include repeated updates and deletions, rather than stopping after the first import. Before redesigning keys or removing indexes, establish which part of the real dataset causes the increase and which queries would become slower without those indexes.</p>
<p>For application-level monitoring, <a href="https://web.dev/articles/storage-for-the-web">the StorageManager API</a> provides usage and quota estimates through <code>navigator.storage.estimate()</code>. These are useful for tracking storage pressure, but they cover more than one database and should not be treated as an exact SQLite file-size measurement. Exercise the write-failure path as well: the interface must preserve the distinction between data saved locally, data still awaiting a successful local write, and data acknowledged by the server.</p>
<h2>Test an established user’s day</h2>
<p>Start with two disposable test environments: a profile carrying existing LevelDB data and a clean profile configured for the new-stores stage. Record the browser build and feature configuration with each result. The staged feature parameter documented in Microsoft’s validation request, and present in Chrome 156’s backend-selection code, is:</p>
<pre><code>--enable-features=IdbSqliteOnDiskRollout:stage/UseSqliteForNewStores</code></pre>
<p>Use that browser argument only in a controlled test launch. It selects the new-stores stage; it does not convert an existing healthy LevelDB store. A configured desktop test can make comparisons repeatable, but mobile behavior still needs checking on the Android devices and browser or embedded WebView builds the product supports.</p>
<p>Use the transaction’s <code>complete</code> event, or its equivalent in the IndexedDB library, to establish successful local commit. An individual request’s <code>success</code> event is earlier in the <a href="https://w3c.github.io/IndexedDB/#transaction-lifecycle">transaction lifecycle</a>; later work can still abort the transaction. Server acknowledgement is a separate application-level milestone.</p>
<p>Run the same user journey in both environments:</p>
<ol>
<li><strong>Populate realistic history.</strong> Include older records, real index definitions, deleted content, attachments, and pending uploads. Inspect the saved records and indexes in the <a href="https://developer.chrome.com/docs/devtools/storage/indexeddb">DevTools Application panel</a>.</li>
<li><strong>Work offline while the database is busy.</strong> Save and edit user content during a background import. Measure foreground save completion and verify that the interface does not declare success prematurely.</li>
<li><strong>Leave and return.</strong> Reopen the application and verify committed records and attachment contents. In the disposable environment, also interrupt work before commit and confirm that recovery does not present an incomplete operation as finished.</li>
<li><strong>Exercise updates and failures.</strong> Run the application’s real schema upgrade against established data, test quota-related write failures, and check how dependent features behave when a required database is unavailable.</li>
<li><strong>Reconnect and reconcile.</strong> Confirm that locally committed work reaches the server, that retried uploads do not create duplicate user actions, and that the interface distinguishes pending work from acknowledged work.</li>
</ol>
<p>The application also still needs to save while the user is working. Mobiletech’s explanation of <a href="https://www.mobiletech.mobi/web-page-lifecycle-unload-bfcache-visibility/">the unreliable end of a web page</a> covers why an exit callback cannot carry that responsibility. For valuable local data, <a href="https://web.dev/articles/persistent-storage">persistent storage</a> can reduce exposure to eviction under storage pressure, when granted. It does not turn a browser database into a backup or prevent the user from clearing site data.</p>
<p>A site that passes these checks can keep its IndexedDB interface and let the browser’s staged transition proceed. The decision to change batching, indexes, or recovery behavior should follow an observed problem with the application’s own data. For someone returning to the app after a day offline, the result that matters is straightforward: the draft is still there, the attachment opens, and work waiting to upload has not disappeared.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Chrome 156 Makes Failed Module Loads Retryable</title>
		<link>https://www.mobiletech.mobi/chrome-156-failed-module-load-retries/</link>
		
		<dc:creator/>
		<pubDate>Wed, 21 Oct 2026 10:19:17 +0000</pubDate>
				<category><![CDATA[Mobile Web]]></category>
		<guid isPermaLink="false">http://localhost:18082/?p=524</guid>

					<description><![CDATA[Chrome 156 lets mobile sites retry failed module downloads. Learn what can recover, what still fails, and how to test the same open page.]]></description>
										<content:encoded><![CDATA[<p>Chrome 156 for Android gives mobile websites a way to recover from a frustrating failure: a feature’s JavaScript fails to download, the connection comes back, but trying again still produces an error. Google <a href="https://chromereleases.googleblog.com/2026/10/chrome-for-android-update_095004068.html">announced the stable Android release on October 20</a>, with distribution through Google Play over the following few days. Its module-loading change can let a site restore the missing feature while keeping the rest of the page open.</p>
<p>The change appears in the <a href="https://chromestatus.com/release-notes/156">Chrome 156 release notes</a> as “Avoid caching module failures.” After a failed module fetch, a fresh <code>import()</code> can try loading the same resource again. The first failure still needs handling, and the site must initiate the next attempt. Chrome does not automatically retry a broken checkout panel, restart a component, or make an unavailable file work offline.</p>
<h2>What is actually enabled in Chrome 156</h2>
<p>Google’s tracker and the released code disagree. As of October 21, the <a href="https://chromestatus.com/api/v0/features/5214647044145152">ChromeStatus feature record</a> assigns milestone 156 to desktop Chrome, Android, and Android WebView, but still labels the feature <em>Proposed</em> and reports it as unreleased.</p>
<p>The configuration for <a href="https://chromium.googlesource.com/chromium/src/+/156.0.8078.47/third_party/blink/renderer/platform/runtime_enabled_features.json5">Android’s announced build, 156.0.8078.47</a>, marks <code>ModuleMapDoNotCacheFailedFetch</code> as <code>stable</code>, without a separate Android override. Chromium’s <a href="https://github.com/chromium/chromium/blob/main/third_party/blink/renderer/platform/RuntimeEnabledFeatures.md">feature configuration documentation</a> explains that stable features are enabled by default. That supports availability in the released 156 build despite the conflicting tracker status.</p>
<p>Browser distribution is still gradual. A visitor using an older installed version does not gain the behavior because the release date has passed. The Android announcement also establishes no availability in Chrome on iOS. For an embedded Android WebView, verify its installed version and behavior separately rather than assuming the Chrome app’s update covers it.</p>
<h2>Why a recovered connection could leave a feature broken</h2>
<p>Consider a shopping page that loads its delivery-options panel only when someone opens it. The product page is already usable, but the panel needs another JavaScript module. If that download fails during a connection interruption, the site can display an error and offer another attempt. The important question is whether that attempt can actually reach the resource.</p>
<p>Under the previous behavior described in the <a href="https://gist.github.com/yoavweiss/a54501545ab2ad22bfde0ebed02a115c">feature explainer</a>, a failed fetch left an entry in the browser’s module map, keyed by the module’s URL and type. Later imports of the same module encountered that stored failure before another fetch could begin. The failure could therefore outlast the connection problem for the lifetime of the document or worker.</p>
<p>Removing that failed entry changes what happens next. Once the first attempt has failed, a later import can start another fetch. In the shopping example, this gives the site a route to reopen the delivery panel without rebuilding the entire page. Preserving the shopper’s selections remains the application’s responsibility, but a full reload need not be its first recovery action.</p>
<p>Timing matters. The <a href="https://github.com/whatwg/html/pull/10327">HTML standard change</a> preserves sharing between concurrent imports: requests for the same module while its fetch is in progress still share that attempt and can fail together. Starting several imports simultaneously is not a sequence of independent retries. The useful second attempt comes after the failed one has settled.</p>
<h2>Which failures can benefit</h2>
<p>The relevant boundary is obtaining a usable module response. The <a href="https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-single-module-script">HTML module-fetch algorithm</a> removes the failed entry for a network or response-body failure, an unsuccessful HTTP status, or a response that cannot produce the requested module type. Chromium’s feature configuration also explicitly identifies MIME-type mismatches.</p>
<p>That makes a temporary connection failure a useful retry candidate. A temporary server error can also be recoverable if the server subsequently returns the file. A wrong content type can be retried after the response is corrected. In each case, removing the remembered failure creates an opportunity; the resource still has to become available in a usable form.</p>
<p><strong>Syntax errors and execution errors are different.</strong> A downloaded JavaScript file with invalid syntax retains its parse error under the <a href="https://html.spec.whatwg.org/multipage/webappapis.html#creating-a-javascript-module-script">module-creation rules</a>. A module that requests an export its dependency does not provide has a linking problem. Neither problem is repaired by restoring the connection and repeating the same import.</p>
<p>Likewise, the <a href="https://tc39.es/ecma262/multipage/ecmascript-language-scripts-and-modules.html#sec-cyclic-module-records-evaluate">JavaScript module-evaluation rules</a> retain failures that occur while the module runs. For example, a module might download correctly and then perform a top-level <code>await fetch(...)</code> that rejects. Although the underlying problem involves the network, it occurs during module execution. Importing that same module again does not restart its failed initialization. Applying these rules, a team should put recoverable data loading behind an explicitly repeatable operation instead of expecting another import to rerun it.</p>
<h2>The retry button has to reach the browser</h2>
<p>A browser-level improvement can be hidden behind an application-level cache. <a href="https://react.dev/reference/react/lazy">React’s documentation for <code>lazy</code></a> says it caches the loader’s returned Promise and does not call that loader more than once for the same lazy component. If the Promise rejects, React passes the rejection to the nearest Error Boundary.</p>
<p>The practical consequence is that resetting an error display alone does not establish a new loading attempt. A Retry button can render the same lazy component and encounter the same rejected Promise without invoking another native <code>import()</code>. Inspect the loader and the network activity together. If the loader never runs again, recovery needs attention at the framework or application layer. Creating a new lazy component on every render is not a sound shortcut: React’s troubleshooting guidance warns that this resets component state.</p>
<p>Also inspect the code sent to the phone. An <code>import()</code> in source code does not prove that the production bundle uses the browser’s native module loader. <a href="https://webpack.js.org/configuration/output/#outputchunkloading">Webpack supports several chunk-loading mechanisms</a>, including JSONP and native import, depending on its output configuration. A runtime that loads chunks through another mechanism has its own failure behavior. Test the production build and its actual retry path.</p>
<p>For the interface itself, a reasonable starting point is a visible error beside the failed feature, a deliberate Retry action, and a loading state while that attempt runs. Keep the already working parts of the page usable. If the application schedules retries itself, bound them and provide a way to stop waiting; this browser change supplies no retry timing or user interface.</p>
<h2>A second request cannot restore a deleted file</h2>
<p>Some apparent connection failures are deployment failures. <a href="https://vite.dev/guide/build#load-error-handling">Vite’s production guide</a> describes an open page requesting an old JavaScript chunk after a new deployment has deleted it. The guide exposes <code>vite:preloadError</code> for application handling and shows reloading as one possible response. It does not promise automatic recovery.</p>
<p>If the old chunk is gone, asking for the same URL again still cannot retrieve it. Teams need a deployment policy that preserves assets required by open sessions, or a controlled transition to the new page version. An update prompt may be appropriate there. A transient connection failure calls for a different response: give the existing page a chance to finish loading its feature.</p>
<p>Other caches also remain relevant. Google’s <a href="https://developer.chrome.com/docs/workbox/caching-strategies-overview">service-worker caching guidance</a> distinguishes the HTTP cache from application-managed caches and shows how a service worker can answer requests itself. Removing a module-map failure does not clear those layers or guarantee a trip to the origin server. A new load attempt still follows the site’s delivery and caching behavior.</p>
<p>This also sets the offline limit. If the module is unavailable both locally and over the network, repeated imports cannot supply its code. A site that needs the feature offline must arrange for the necessary resources to be available beforehand. Chrome 156’s change helps recovery when resources become obtainable again.</p>
<h2>Test the interruption, then test the recovery</h2>
<p>The useful test is whether the same open page can recover the same module URL after a controlled failure. Chrome’s <a href="https://developer.chrome.com/docs/devtools/remote-debugging/">Android remote-debugging tools</a> let a team inspect the page on a real phone. The <a href="https://developer.chrome.com/docs/devtools/request-conditions">DevTools Request conditions panel</a> can selectively block a resource and then remove that block, avoiding the need to make every request on the page fail.</p>
<ol>
<li><strong>Start with the production build and record the installed browser version.</strong> Choose a feature whose module has not already loaded. Account for prefetching and module preloads so the first attempt genuinely needs the resource.</li>
<li><strong>Load the surrounding page, then fail the chosen module request.</strong> Block its URL or use a controlled endpoint that temporarily returns an HTTP error. Confirm that the import rejects and the page displays its recovery interface.</li>
<li><strong>Restore access without reloading the document.</strong> Keep the module URL unchanged and trigger the application’s Retry action. Reusing an old rejected Promise will not exercise the new browser behavior.</li>
<li><strong>Verify both the new load attempt and the restored feature.</strong> Inspect the request and its initiator, then complete the original interaction. Check that selections or entered information elsewhere on the page survive.</li>
</ol>
<p>For a controlled HTTP-error test, prevent the test response from being reused by the HTTP cache, so the result isolates module-loader behavior. Then repeat with the application’s normal cache headers and service worker. Reloading the page or adding a new query string between attempts changes the conditions and does not demonstrate recovery for the same module in the same document.</p>
<p>Repeat the test with a failed dependency inside the lazy feature, not just its entry file. A panel can fetch its own module successfully while a helper module fails. The <a href="https://github.com/web-platform-tests/wpt/pull/55505/files">Web Platform Tests added for this change</a> include repeated-import and shared-dependency cases; an application’s production loading graph deserves the same attention.</p>
<p>Finally, keep the failure in place and check the stopping behavior. Try a deleted chunk, malformed JavaScript, and a module that throws during initialization. These are useful controls: the page should offer an understandable next step instead of an endless spinner. Repeat relevant checks in the other browsers and embedded environments the site supports.</p>
<h2>Judge success by the restored interaction</h2>
<p>For a site team, the useful outcome is that someone can finish the action they started. Record the initial module-load failure, whether a fresh attempt occurred, whether the feature became usable, and whether a reload was necessary. Keep those outcomes separate: a click on Retry does not establish that a new request happened, and a successful request does not establish that the interface recovered.</p>
<p>Chrome 156 removes one obstacle between a temporary download failure and another attempt. The remaining work is specific: make sure the loader can run again, keep the requested asset available, and verify that the recovered feature lets the visitor continue. A phone regaining its connection should be an opportunity to finish the task already on the screen.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Safari 27 Identity Blocking: Publisher Checks</title>
		<link>https://www.mobiletech.mobi/safari-27-identity-resolution-publishers/</link>
		
		<dc:creator/>
		<pubDate>Tue, 20 Oct 2026 23:23:42 +0000</pubDate>
				<category><![CDATA[Mobile Publishing]]></category>
		<guid isPermaLink="false">http://localhost:18082/?p=519</guid>

					<description><![CDATA[Safari 27 blocks targeted identity-resolution connections. What mobile publishers should verify in newsletter signups, logins, advertising, and measurement.]]></description>
										<content:encoded><![CDATA[<p>Safari now blocks browser connections to targeted identity-resolution domains, cutting off a route that turns an email address into a way to recognize someone across websites. For a mobile publisher, that puts a practical question behind every login box and newsletter form: can the reader finish the job if an advertising identity service never answers?</p>
<p>WebKit’s <a href="https://webkit.org/blog/18438/blocking-identity-resolution-infrastructure/">October 19 explanation</a> describes default protection already shipped in Apple’s OS 27 releases. Those operating systems became publicly available on <a href="https://support.apple.com/en-us/100100">September 14, 2026</a>. October 19 is the explanation’s publication date, not a new rollout date. Teams investigating a change in Safari traffic should examine behavior since the operating-system release and subsequent updates.</p>
<h2>Where the protection applies</h2>
<p>WebKit identifies Safari and WebKit in iOS 27, iPadOS 27, macOS 27, and visionOS 27. Safari 27.0 accompanies those releases. WebKit’s <a href="https://webkit.org/blog/18325/webkit-features-for-safari-27-0/">Safari 27 overview</a> also lists a separate Safari update for macOS 26 Tahoe and macOS 15 Sequoia, but browser availability on those older systems does not by itself establish identical identity-resolution protection there.</p>
<p>Use the operating system, browser version, and browsing environment when investigating an affected visit. A Safari version number alone is an incomplete compatibility description. The announcement supplies no separate regional rollout timetable or comprehensive list of embedded-browser configurations. If readers reach your publication through an app’s web view or an installed web app, include that actual route in your checks.</p>
<h2>Why email matching survives third-party cookie blocking</h2>
<p>Safari’s default blocking of third-party cookies dates to <a href="https://webkit.org/blog/10218/full-third-party-cookie-blocking-and-more/">iOS and iPadOS 13.4 and Safari 13.1 in March 2020</a>. That closed an important channel for recognizing a browser across sites. It did not make an email address stop being useful for matching records.</p>
<p>In the scheme WebKit describes, a third party receives an address around a login or newsletter signup and returns an opaque identifier. Different websites can hold different identifiers while the resolver retains the connection to the same address. Keeping the returned value in a site’s own storage does not remove that central matching capability. Nor does logging out or unsubscribing erase the third party’s existing records.</p>
<p>Hashing the address changes its representation, but can preserve precisely the consistency a matching system needs. <a href="https://unifiedid.com/docs/getting-started/gs-normalization-encoding">Unified ID 2.0’s normalization documentation</a>, for example, explains how correctly processing the same email address produces matching identifiers. Its email input can be normalized, hashed with SHA-256, and Base64-encoded. An unreadable string is therefore not evidence that records cannot be connected.</p>
<p>These connections can sit inside a publication’s existing integrations. <a href="https://docs.liveramp.com/privacy-manager/en/configure-how-identifiers-are-obtained.html">LiveRamp’s ATS documentation</a> describes both directly supplied identifiers and detection through page content or URLs, including a newsletter widget as a possible source. A review should follow where an address goes after collection, including scripts that observe the form, rather than end with the company that delivers the email.</p>
<h2>The browser connection is the boundary</h2>
<p>The protection concerns the destination of a request. WebKit’s <a href="https://github.com/WebKit/WebKit/pull/68138">public implementation change from July</a> describes destination-based blocking of targeted subresources. The <a href="https://github.com/WebKit/WebKit/pull/68138/files">associated code</a> excludes main-frame navigations and requests to the same site as the top-level page from that particular blocking path. Those distinctions matter when reproducing a failure: opening a vendor’s address directly is a different operation from having an embedded integration contact it from a publisher’s page.</p>
<p>A failing background request also needs to be identified accurately. The <a href="https://github.com/charliewolfe/WebKit/blob/038f53fa65bf2d41bdc7b1b6639a7fef5180db68/Source/WebKit/NetworkProcess/NetworkLoadChecker.cpp">network-checking implementation</a> returns an access-control error with the message <code>Blocked by tracking protections</code> when this check rejects a request. Capture the destination, initiating script, and error on the affected browser. A missing identifier by itself does not tell you whether the cause was blocking, consent configuration, initialization timing, or an ordinary service failure.</p>
<p>This is a browser-side boundary. If a publisher’s server receives information and later sends it to another server, that later transfer happens outside the browser connection being blocked. It follows that a successful Safari check cannot certify the publication’s entire data flow. Review backend integrations as well; network blocking should not be mistaken for deletion of a profile already held elsewhere.</p>
<h2>A newsletter subscription should have its own success condition</h2>
<p>WebKit explicitly distinguishes the targeted identity infrastructure from login providers and newsletter services. Its <a href="https://webkit.org/tracking-prevention-policy/">Tracking Prevention Policy</a> also identifies federated login, organizational single sign-on, single-site analytics, and advertising measurement as practices it does not intend to disrupt. The policy nevertheless acknowledges that shared techniques can produce unintended effects.</p>
<p>For a publication, the design requirement is straightforward: the reader-facing transaction needs a success condition independent of advertising identification. A newsletter signup should be acknowledged when the subscription service accepts the request; count the subscription as confirmed only after any required confirmation is complete. A login succeeds when the account session is established. An optional identity response should not decide whether the page displays either result.</p>
<p>That means examining error handling as closely as the happy path. If a resolver fails, does the signup button keep spinning? Does a successful account creation get presented as an error? Does retrying the page resubmit the subscription? These are integration checks to perform, not consequences that should be assumed for every site using an identity provider.</p>
<h3>Start checking before the Submit button</h3>
<p>Waiting until submission can miss part of the data flow. The <a href="https://www.usenix.org/conference/usenixsecurity22/presentation/senol">2022 USENIX study “Leaky Forms”</a> documented third-party collection of email addresses before people submitted forms, including visits where users could leave without submitting. It compared desktop and mobile configurations and EU and US vantage points. That historical research does not measure today’s Safari protection, but it gives teams a concrete reason to inspect requests while typing, moving between fields, submitting, and abandoning a form.</p>
<h3>Follow the email back to the site</h3>
<p>A reader can also enter an identity flow by clicking a newsletter. <a href="https://docs.liveramp.com/privacy-manager/en/email-service-providers--esps-.html">LiveRamp’s email-service-provider guidance</a> describes integrations that obtain identifiers from email links or use a hashed email stored in local storage or a first-party cookie. It also warns that forwarded messages can identify the original recipient rather than the person who clicks. An email click is therefore not interchangeable with a fresh, authenticated login.</p>
<p>Check the landing page and the subsequent background requests separately. Safari already has <a href="https://webkit.org/blog/15697/private-browsing-2-0/">Link Tracking Protection and known-tracker blocking associated with its enhanced browsing protections</a>. Link Tracking Protection can remove selected identifying URL parameters; identity-resolution blocking concerns access to targeted infrastructure. If a newsletter visit loses a parameter or an identity response, first establish which operation changed. Do not treat every missing value as evidence of this OS 27 feature.</p>
<p>Include email aliases in the journey. Apple’s <a href="https://support.apple.com/guide/icloud/what-you-can-do-with-icloud-and-hide-my-email-mme38e1602db/1.0/icloud/1.0">Hide My Email documentation</a> explains that iCloud+ subscribers can generate random addresses whose messages are forwarded to their personal inbox. Test confirmation, password recovery, email sign-in links, and unsubscribe using the address actually supplied. An alias can support a continuing relationship with a publication without exposing the reader’s usual address.</p>
<h2>Advertising can expose a shared-domain problem</h2>
<p>A blocked identity request and a blocked ad-delivery request have different consequences. A <a href="https://bugs.webkit.org/show_bug.cgi?id=324771">public WebKit compatibility report filed September 21</a> provides a concrete example. The reporter said that <code>adsrvr.org</code>, which he identified as The Trade Desk’s core ad-request and delivery domain, was being blocked on iOS 27 in ordinary, non-private browsing. WebKit’s John Wilander acknowledged the investigation, asked the reporter to test an iOS 27.2 beta on October 5, and confirmed the issue resolved on October 9.</p>
<p>That record establishes a reported compatibility problem and a resolution following beta testing. It does not establish that every stable installation has the correction. As of October 21, <a href="https://support.apple.com/en-us/100100">Apple’s public release listing</a> identifies iOS 27.0.1 as the latest stable iOS version. Publishers investigating this dependency need to record the exact build and reproduce the behavior on the version their readers actually use.</p>
<p>The practical lesson is to ask an advertising partner what each failing hostname does. Does it issue an identifier, retrieve a bid, deliver the creative, or report an outcome? A request’s role determines whether the appropriate response is to accept an absent identity signal, fix a broken delivery dependency, or investigate a measurement gap. A vendor name alone cannot answer that question.</p>
<p>Also check the order in which integrations run. <a href="https://docs.prebid.org/dev-docs/modules/userId.html">Prebid’s User ID documentation</a> notes that its ID-reading functions can return only some identifiers, or none, before providers finish initializing. An empty result is not automatically a Safari block. Once a blocked dependency is confirmed, verify how your configuration handles the missing identifier and whether the auction and page can proceed without waiting indefinitely.</p>
<h2>Count completed actions separately from recognized people</h2>
<p>A useful measurement review keeps three outcomes distinct:</p>
<ul>
<li><strong>The reader completed the action.</strong> The account, subscription, or purchase exists in the system responsible for it.</li>
<li><strong>The measurement system received an event.</strong> The analytics endpoint actually recorded the relevant action.</li>
<li><strong>The event could be matched or attributed.</strong> A reporting system had enough identity or attribution information to connect it to another visit, device, or campaign.</li>
</ul>
<p>If subscriptions remain steady while an identity partner recognizes fewer visitors, that pattern is consistent with reduced matching coverage. It does not establish a loss of readers or paid subscriptions. Conversely, a form that waits forever on a rejected request is a user-facing failure, even if the advertising dashboard is the first place someone notices trouble. Confirm the transaction before interpreting the chart.</p>
<p>Compare those outcomes within meaningful browser and operating-system groups, over comparable periods. Changes in traffic sources, consent choices, or a supplier’s configuration can otherwise be confused with the browser update. Keep actual sales or subscription records alongside attributed conversions; a change in how an outcome is credited is not a direct measurement of whether it occurred.</p>
<p>Delivery APIs do not collapse these distinctions. The <a href="https://www.w3.org/TR/beacon/">W3C Beacon specification</a> says that <code>sendBeacon()</code> returning <code>true</code> means data was queued, not that the destination received it. Mobiletech’s guide to <a href="https://www.mobiletech.mobi/web-page-lifecycle-unload-bfcache-visibility/">analytics delivery as mobile pages become hidden or disappear</a> addresses that separate transport problem. Improving delivery timing cannot make a blocked destination available or restore a missing cross-site identity.</p>
<h2>What to verify on a mobile visit</h2>
<p>Begin with the reader’s complete journey and the dependencies it actually uses. A focused check should cover four things:</p>
<ol>
<li><strong>Record the environment.</strong> Use a real affected iPhone or iPad browser, note the OS and browser build, and check ordinary and private browsing separately. Record extensions and relevant privacy settings. Treat a beta result as a separate result from the shipping version.</li>
<li><strong>Complete the whole task.</strong> Create an account, confirm a newsletter subscription, sign out and back in, follow an email sign-in link, recover access, and unsubscribe where those flows exist. Verify the service’s records and the reader’s visible result, including any required email delivery.</li>
<li><strong>Trace the failing dependency.</strong> Inspect requests from the actual page, including those triggered before submission and after a newsletter click. Identify the hostname, purpose, initiating integration, and failure. Use a test account rather than collecting production readers’ addresses for debugging.</li>
<li><strong>Verify the fallback.</strong> Check that the core transaction can complete with the optional identity integration unavailable. Confirm how advertising proceeds without that identifier and what the resulting reports mean. Give a supplier a reproducible failure with the exact version and request context when an essential service is affected.</li>
</ol>
<p>If the reader’s task completes, ads behave as intended, and the only change is a documented reduction in cross-site matching, there may be no user-facing defect to repair. If an essential flow fails, waiting for a broadly described future fix is insufficient: identify the dependency and verify the correction on a release your audience can use. A reader who asks for a newsletter should be able to receive it even when a third party never receives an advertising identity.</p>
]]></content:encoded>
					
		
		
			</item>
		<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>