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

<channel>
	<title>Weston Ruter</title>
	<atom:link href="https://weston.ruter.net/feed/" rel="self" type="application/rss+xml" />
	<link>https://weston.ruter.net/</link>
	<description>Building a Better Web</description>
	<lastBuildDate>Sun, 25 Jan 2026 06:46:47 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9</generator>

<image>
	<url>https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/site-icon.png?fit=32%2C32&#038;ssl=1</url>
	<title>Weston Ruter</title>
	<link>https://weston.ruter.net/</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">59681501</site>	<item>
		<title>Post Date Block: Published &#038; Modified</title>
		<link>https://weston.ruter.net/2026/01/19/post-date-block-published-modified/</link>
					<comments>https://weston.ruter.net/2026/01/19/post-date-block-published-modified/#comments</comments>
		
		<dc:creator><![CDATA[Weston Ruter]]></dc:creator>
		<pubDate>Tue, 20 Jan 2026 05:45:15 +0000</pubDate>
				<category><![CDATA[WordPress]]></category>
		<guid isPermaLink="false">https://weston.ruter.net/?p=38580</guid>

					<description><![CDATA[<p>Development of a WordPress plugin which appends the modified date to the Post Date block when it is different from the published date. </p>
<p>The post <a href="https://weston.ruter.net/2026/01/19/post-date-block-published-modified/">Post Date Block: Published &amp; Modified</a> appeared first on <a href="https://weston.ruter.net">Weston Ruter</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>I&#8217;ve had an itch I&#8217;ve wanted to scratch for a while. A year ago, I re-developed my blog here to use the Twenty Twenty-Five block theme replacing the Twenty Twenty classic theme. Something I wanted to do was show a post&#8217;s published date followed by the modified date if it differed. For example, for WCUS 2024 I published <a href="https://weston.ruter.net/2024/09/12/my-portland-picks/">My Portland Picks</a> to share my favorite things to do in the city. Since WCUS 2025 was also in Portland last year, I updated the post with my latest picks. To make it clear it had been changed, I manually added a “Post updated for 2025” paragraph at the beginning of the post. But I wanted to avoid having to keep doing this for such evergreen content.</p>



<p>There are two blocks listed in the block inserter for dates: <a href="https://wordpress.org/documentation/article/post-date-block/">Date</a> and <a href="https://wordpress.org/documentation/article/post-modified-date-block/">Modified Date</a>. (Previously these block names started with “Post” but these were <a href="https://github.com/WordPress/gutenberg/pull/53492">removed</a> in WordPress 6.4.) In reality, these two blocks are just variations of the one Date block. You could turn a Date block into a Modified Date block by enabling this block setting:</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bcdf8acde&quot;}" data-wp-interactive="core/image" data-wp-key="6975bcdf8acde" class="wp-block-image size-large has-custom-border wp-lightbox-container"><img data-recalc-dims="1" data-dominant-color="f3f3f3" data-has-transparency="false" fetchpriority="high" decoding="async" width="700" height="225" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="(max-width: 645px) 100vw, 645px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2026/01/display-last-modified-date.png?resize=700%2C225&#038;ssl=1" alt="Display last modified date toggle.

Only shows if the post has been modified." class="wp-image-38581 not-transparent" style="--dominant-color: #f3f3f3; border-width:1px;box-shadow:var(--wp--preset--shadow--natural)" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2026/01/display-last-modified-date.png?resize=700%2C225&amp;ssl=1 700w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2026/01/display-last-modified-date.png?resize=300%2C96&amp;ssl=1 300w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2026/01/display-last-modified-date.png?resize=768%2C247&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2026/01/display-last-modified-date.png?resize=1536%2C493&amp;ssl=1 1536w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2026/01/display-last-modified-date.png?resize=2048%2C658&amp;ssl=1 2048w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2026/01/display-last-modified-date.png?resize=150%2C48&amp;ssl=1 150w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<p>With block bindings in WordPress 6.9, the Date block became <a href="https://github.com/WordPress/gutenberg/pull/70585">further abstracted</a> to be able to represent any date, not just a post&#8217;s publish date or modified date. When a Date block is inserted, you can now decide to bind the block to either of these dates:</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bcdf8b95f&quot;}" data-wp-interactive="core/image" data-wp-key="6975bcdf8b95f" class="wp-block-image size-large has-custom-border wp-lightbox-container"><img data-recalc-dims="1" data-dominant-color="f4f6f7" data-has-transparency="false" decoding="async" width="621" height="700" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="(max-width: 621px) 100vw, 621px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2026/01/date-block-variations.png?resize=621%2C700&#038;ssl=1" alt="Date block settings, with the transform to variation menu opened showing the Post Date and Modified Date as options. Instead of displaying a custom date, the variations allow showing the post's publish date or the post's last updated date." class="wp-image-38582 not-transparent" style="--dominant-color: #f4f6f7; border-width:1px;box-shadow:var(--wp--preset--shadow--natural)" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2026/01/date-block-variations.png?resize=621%2C700&amp;ssl=1 621w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2026/01/date-block-variations.png?resize=266%2C300&amp;ssl=1 266w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2026/01/date-block-variations.png?resize=768%2C865&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2026/01/date-block-variations.png?resize=1363%2C1536&amp;ssl=1 1363w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2026/01/date-block-variations.png?resize=1818%2C2048&amp;ssl=1 1818w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2026/01/date-block-variations.png?resize=150%2C169&amp;ssl=1 150w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2026/01/date-block-variations.png?w=1878&amp;ssl=1 1878w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<p>The key word there is “either”. The setting I was looking for was missing: the ability to display the publish date <em>and</em> the modified date (if it differs).</p>



<p>I did find Gutenberg issue <a href="https://github.com/WordPress/gutenberg/issues/53099">#53099</a> opened by Carolina Nymark where she was reporting that it was confusing that the old “display modified date” toggle actually causes the modified date to be displayed instead of the publish date:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>The info I was looking for was whether, when toggled on, it would display both the published and modified dates, or display the modified date instead of the published date, as from the settings alone it’s not immediately clear</p>
</blockquote>



<p>Commenting on that issue, Ronnie Burt <a href="https://github.com/WordPress/gutenberg/issues/53099#issuecomment-2855553546">said</a>:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Feedback after using this block on multiple sites in templates for both posts and pages. The &#8216;modified date&#8217; block doesn&#8217;t show anything if the post has never been updated. This makes it really challenging to use, as there will be a blank spot on the front end for new posts or pages without any edits.</p>



<p>It would be more useful to show the date published if the post has no edits.</p>
</blockquote>



<p>Carolina <a href="https://github.com/WordPress/gutenberg/issues/53099#issuecomment-2856811501">responded</a>:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>The problem is that there is no one scenario that will work for&nbsp;<strong>all</strong>&nbsp;use cases. <mark style="background-color:#FFEE58" class="has-inline-color">Some users and designs wants to display both the original publishing date and the modified date.</mark> So no matter how this block is updated, it will break for&nbsp;<strong>someone</strong>.</p>



<p>Your best option may be to register a block binding with a callback function that displays the post date, to get the exact conditions that&nbsp;<strong>you</strong>&nbsp;need, and then insert a paragraph with this block binding, instead of using the post date block.</p>
</blockquote>



<p>A few months later, when I discovered the issue, I <a href="https://github.com/WordPress/gutenberg/issues/53099#issuecomment-3194738185">commented</a>:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>To me it seems like the block should facilitate showing the published date and then also show the modified date&nbsp;<em>if</em>&nbsp;it is different, with a prefix like &#8220;Updated: &#8221; or some other parenthetical treatment. I don&#8217;t think the modified date should ever take over the published date. I&#8217;m not sure the best way to achieve that other than by adding a plugin which filters the Date block to append the desired content. I&#8217;m not sure how that could be handled in the UI, given we&#8217;re dealing with inline content and there is no ability to hide blocks (cf.&nbsp;<a href="https://github.com/WordPress/gutenberg/issues/50756">#50756</a>), and there&#8217;d have to be a condition like &#8220;Only show this block if the rendered date/time of the published date is not the same as the rendered date/time of the modified date.&#8221;</p>
</blockquote>



<p>So this is what I wanted, but the Date block didn&#8217;t support it. So I started exploring something that would implement my desired use case.</p>



<p>Accounting for all the different scenarios adds complexity to the block. Rendering a single publish date or modified date is simple enough. But if the modified date needs to be displayed <em>conditionally</em> then how is this configured in the editor? Namely, if both dates are displayed, then one or both of the dates would need to have some prefix indicating what the date refers to. The two configurations I had in mind were prefixing the modified date only:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>December 25, 2025 (Modified: January 1, 2026)</p>
</blockquote>



<p>Or else putting each on a separate line and having prefixes for each:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Published: December 25, 2025<br>Modified: January 1, 2026</p>
</blockquote>



<p>There is an existing Gutenberg issue <a href="https://github.com/WordPress/gutenberg/pull/61920">#61920</a> which is about adding prefixes and suffixes to the block similarly to the <a href="https://github.com/WordPress/gutenberg/pull/40559">Categories block</a>. But these prefixes are unconditional for only a single date. I wanted these prefixes added conditionally for when the modified date is displayed in addition to the published date.</p>



<h2 class="wp-block-heading">A New Plugin</h2>



<p>Ultimately, I put together a plugin to implement what I wanted: <a href="https://github.com/westonruter/post-date-modified"><strong>Post Date Block: Published &amp; Modified</strong></a>. It&#8217;s currently on GitHub but I&#8217;ve also submitted it to the WordPress.org directory. </p>



<figure class="wp-block-image aligncenter size-large is-resized"><a href="https://playground.wordpress.net/?blueprint-url=https%3A%2F%2Fraw.githubusercontent.com%2Fwestonruter%2Fpost-date-modified%2Fmain%2F.wordpress-org%2Fblueprint%2Fblueprint.json" target="_blank" rel=" noreferrer noopener"><img decoding="async" width="447" height="104" sizes="(max-width: 235px) 100vw, 235px" src="https://weston.ruter.net/wp-content/uploads/2026/01/playground-preview-button.svg" alt="Try it in Playground" class="wp-image-38587" style="width:235px;height:auto"/></a></figure>



<p><em><strong>Update:</strong> It&#8217;s now live on plugin directory!</em></p>



<figure class="wp-block-embed is-type-wp-embed is-provider-plugin-directory wp-block-embed-plugin-directory"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="jA9fi4uuTo"><a href="https://wordpress.org/plugins/post-date-modified/">Post Date Block: Published &amp; Modified</a></blockquote><iframe loading="lazy" class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  title="&#8220;Post Date Block: Published &amp; Modified&#8221; &#8212; Plugin Directory" src="https://wordpress.org/plugins/post-date-modified/embed/#?secret=b9Sp7SsLaB#?secret=jA9fi4uuTo" data-secret="jA9fi4uuTo" width="500" height="282" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>



<p>Once active, when you are editing the settings for the Date block for the Post Date variation, a new “With Modified Date”&nbsp;panel appears. Inside that panel there is a new toggle:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Show modified date when different from published date</p>
</blockquote>



<p>The two dates are rendered in the supplied date format, and only if they then differ will the modified date be displayed after the published date. This prevents the modified date from appearing as a duplicate when the post is updated on the same day it was published.</p>



<p>When that toggle is enabled, two new sets of fields are presented for adding a prefix and suffix to both the published date and the modified date. There is also a toggle for whether the modified date should be displayed on a separate line. Here are the settings for the two configurations I mentioned previously, one where the modified date is displayed on a separate line and another where it is displayed on the same line:</p>



<div class="wp-block-columns is-not-stacked-on-mobile is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bcdf8def4&quot;}" data-wp-interactive="core/image" data-wp-key="6975bcdf8def4" class="wp-block-image aligncenter size-large wp-lightbox-container"><img data-recalc-dims="1" data-dominant-color="f3f5f6" data-has-transparency="false" loading="lazy" decoding="async" width="254" height="700" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 254px) 100vw, 254px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2026/01/post-date-modified-two-lines.png?resize=254%2C700&#038;ssl=1" alt="Settings for the Post Date block where the modified date is shown when it is different than the published date. The modified date is displayed on a separate line after the published date. Both the published date and modified date have &quot;Published&quot; and &quot;Modified&quot; prefixes." class="wp-image-38585 not-transparent" style="--dominant-color: #f3f5f6; box-shadow:var(--wp--preset--shadow--natural)" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2026/01/post-date-modified-two-lines.png?resize=254%2C700&amp;ssl=1 254w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2026/01/post-date-modified-two-lines.png?resize=109%2C300&amp;ssl=1 109w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2026/01/post-date-modified-two-lines.png?resize=768%2C2116&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2026/01/post-date-modified-two-lines.png?resize=557%2C1536&amp;ssl=1 557w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2026/01/post-date-modified-two-lines.png?resize=743%2C2048&amp;ssl=1 743w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2026/01/post-date-modified-two-lines.png?resize=150%2C413&amp;ssl=1 150w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2026/01/post-date-modified-two-lines.png?w=842&amp;ssl=1 842w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bcdf8ea35&quot;}" data-wp-interactive="core/image" data-wp-key="6975bcdf8ea35" class="wp-block-image aligncenter size-large has-custom-border wp-lightbox-container"><img data-recalc-dims="1" data-dominant-color="f4f5f6" data-has-transparency="false" loading="lazy" decoding="async" width="255" height="700" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 255px) 100vw, 255px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2026/01/post-date-modified-one-line.png?resize=255%2C700&#038;ssl=1" alt="Settings for the Post Date block where the modified date is shown when it is different than the published date. The modified date is displayed on the same line after the published date, and only modified date has a prefix and a suffix to give it a parenthetical rendering." class="wp-image-38584 not-transparent" style="--dominant-color: #f4f5f6; border-style:none;border-width:0px;box-shadow:var(--wp--preset--shadow--natural)" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2026/01/post-date-modified-one-line.png?resize=255%2C700&amp;ssl=1 255w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2026/01/post-date-modified-one-line.png?resize=109%2C300&amp;ssl=1 109w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2026/01/post-date-modified-one-line.png?resize=768%2C2111&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2026/01/post-date-modified-one-line.png?resize=559%2C1536&amp;ssl=1 559w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2026/01/post-date-modified-one-line.png?resize=745%2C2048&amp;ssl=1 745w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2026/01/post-date-modified-one-line.png?resize=150%2C412&amp;ssl=1 150w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2026/01/post-date-modified-one-line.png?w=844&amp;ssl=1 844w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>
</div>
</div>



<p>In terms of the frontend rendering, here&#8217;s what the Date markup looks like right after publishing on Christmas:</p>


<pre class="wp-block-code"><span><code class="hljs language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"wp-block-post-date"</span>&gt;</span>
	<span class="hljs-tag">&lt;<span class="hljs-name">time</span> <span class="hljs-attr">datetime</span>=<span class="hljs-string">"2025-12-25T18:14:21-08:00"</span>&gt;</span>
		December 25, 2025
	<span class="hljs-tag">&lt;/<span class="hljs-name">time</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></code></span></pre>


<p>And here is how the block is rendered after having been modified on New Year&#8217;s Day, in the two-line configuration:</p>


<pre class="wp-block-code"><span><code class="hljs language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"wp-block-post-date post-date-modified"</span>&gt;</span>
	Published:
	<span class="hljs-tag">&lt;<span class="hljs-name">time</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"entry-date published"</span> <span class="hljs-attr">datetime</span>=<span class="hljs-string">"2025-12-25T18:14:21-08:00"</span>&gt;</span>
		December 25, 2025
	<span class="hljs-tag">&lt;/<span class="hljs-name">time</span>&gt;</span>
	<span class="hljs-tag">&lt;<span class="hljs-name">br</span>&gt;</span>
	<span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"modified"</span>&gt;</span>
		Modified:
		<span class="hljs-tag">&lt;<span class="hljs-name">time</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"updated"</span> <span class="hljs-attr">datetime</span>=<span class="hljs-string">"2026-01-01T21:20:05-08:00"</span>&gt;</span>
			January 1, 2026
		<span class="hljs-tag">&lt;/<span class="hljs-name">time</span>&gt;</span>
	<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></code></span></pre>


<p>Note the added microformat classes to disambiguate the published date from the modified date. I was really happy to be able to leverage the <a href="https://developer.wordpress.org/reference/classes/wp_html_tag_processor/">HTML Tag Processor</a> to handle insertion of the prefixes, suffixes, newline, and class names!</p>



<p>This plugin solves my use case, but it&#8217;s primarily a prototype/stopgap until something lands in core. The user experience for this doesn&#8217;t feel ideal, but I can&#8217;t think of a better option at the moment. Note also that there is currently no preview in the editor, so you have to save the changes and go to the frontend to see the results. (My <a href="https://weston.ruter.net/2014/09/27/wcsyd-talk-customize-all-the-things/">Customizer self</a> weeps.) I didn&#8217;t want to fork the Date block&#8217;s edit function to implement full preview in the editor, and it would be of little value anyway since when editing a block template the Date block now just shows ”Invalid date” anyway due to there being no post context for the block binding.</p>



<h2 class="wp-block-heading">The Future</h2>



<p>The ideal solution perhaps would <a href="https://github.com/WordPress/gutenberg/pull/61920#issuecomment-2156095190">involve Bits</a> (now <a href="https://github.com/WordPress/gutenberg/issues/73929">Shortblocks</a>). Maybe the default view for a Post Date block when editing a block template would be to show the view when the modified date is the same as the published date. But then there could potentially be a popover that could allow configuring how the block should render when the modified date is different. With Shortblocks, these tokens could be inserted inline with the content and moved around inside of a paragraph block. This would avoid the need to supply a suffix/prefix in the detached block settings sidebar.</p>



<p>In the meantime, I hope my plugin is helpful for keeping your readers updated!</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="has-medium-font-size">Where I&#8217;ve posted about this:</p>



<ul class="wp-block-social-links is-layout-flex wp-block-social-links-is-layout-flex"><li class="wp-social-link wp-social-link-linkedin  wp-block-social-link"><a href="https://www.linkedin.com/posts/westonruter_post-date-block-published-modified-weston-activity-7419257759004651522-LPT9" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M19.7,3H4.3C3.582,3,3,3.582,3,4.3v15.4C3,20.418,3.582,21,4.3,21h15.4c0.718,0,1.3-0.582,1.3-1.3V4.3 C21,3.582,20.418,3,19.7,3z M8.339,18.338H5.667v-8.59h2.672V18.338z M7.004,8.574c-0.857,0-1.549-0.694-1.549-1.548 c0-0.855,0.691-1.548,1.549-1.548c0.854,0,1.547,0.694,1.547,1.548C8.551,7.881,7.858,8.574,7.004,8.574z M18.339,18.338h-2.669 v-4.177c0-0.996-0.017-2.278-1.387-2.278c-1.389,0-1.601,1.086-1.601,2.206v4.249h-2.667v-8.59h2.559v1.174h0.037 c0.356-0.675,1.227-1.387,2.526-1.387c2.703,0,3.203,1.779,3.203,4.092V18.338z"></path></svg><span class="wp-block-social-link-label screen-reader-text">LinkedIn</span></a></li>

<li class="wp-social-link wp-social-link-bluesky  wp-block-social-link"><a href="https://bsky.app/profile/weston.ruter.net/post/3mcti4zf5as2w" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M6.3,4.2c2.3,1.7,4.8,5.3,5.7,7.2.9-1.9,3.4-5.4,5.7-7.2,1.7-1.3,4.3-2.2,4.3.9s-.4,5.2-.6,5.9c-.7,2.6-3.3,3.2-5.6,2.8,4,.7,5.1,3,2.9,5.3-5,5.2-6.7-2.8-6.7-2.8,0,0-1.7,8-6.7,2.8-2.2-2.3-1.2-4.6,2.9-5.3-2.3.4-4.9-.3-5.6-2.8-.2-.7-.6-5.3-.6-5.9,0-3.1,2.7-2.1,4.3-.9h0Z"></path></svg><span class="wp-block-social-link-label screen-reader-text">Bluesky</span></a></li>

<li class="wp-social-link wp-social-link-threads  wp-block-social-link"><a href="https://www.threads.com/@westonruter/post/DTuMA_mDoGY" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M16.3 11.3c-.1 0-.2-.1-.2-.1-.1-2.6-1.5-4-3.9-4-1.4 0-2.6.6-3.3 1.7l1.3.9c.5-.8 1.4-1 2-1 .8 0 1.4.2 1.7.7.3.3.5.8.5 1.3-.7-.1-1.4-.2-2.2-.1-2.2.1-3.7 1.4-3.6 3.2 0 .9.5 1.7 1.3 2.2.7.4 1.5.6 2.4.6 1.2-.1 2.1-.5 2.7-1.3.5-.6.8-1.4.9-2.4.6.3 1 .8 1.2 1.3.4.9.4 2.4-.8 3.6-1.1 1.1-2.3 1.5-4.3 1.5-2.1 0-3.8-.7-4.8-2S5.7 14.3 5.7 12c0-2.3.5-4.1 1.5-5.4 1.1-1.3 2.7-2 4.8-2 2.2 0 3.8.7 4.9 2 .5.7.9 1.5 1.2 2.5l1.5-.4c-.3-1.2-.8-2.2-1.5-3.1-1.3-1.7-3.3-2.6-6-2.6-2.6 0-4.7.9-6 2.6C4.9 7.2 4.3 9.3 4.3 12s.6 4.8 1.9 6.4c1.4 1.7 3.4 2.6 6 2.6 2.3 0 4-.6 5.3-2 1.8-1.8 1.7-4 1.1-5.4-.4-.9-1.2-1.7-2.3-2.3zm-4 3.8c-1 .1-2-.4-2-1.3 0-.7.5-1.5 2.1-1.6h.5c.6 0 1.1.1 1.6.2-.2 2.3-1.3 2.7-2.2 2.7z"/></svg><span class="wp-block-social-link-label screen-reader-text">Threads</span></a></li>

<li class="wp-social-link wp-social-link-mastodon  wp-block-social-link"><a href="https://mastodon.social/@westonruter/115925902882363377" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M23.193 7.879c0-5.206-3.411-6.732-3.411-6.732C18.062.357 15.108.025 12.041 0h-.076c-3.068.025-6.02.357-7.74 1.147 0 0-3.411 1.526-3.411 6.732 0 1.192-.023 2.618.015 4.129.124 5.092.934 10.109 5.641 11.355 2.17.574 4.034.695 5.535.612 2.722-.15 4.25-.972 4.25-.972l-.09-1.975s-1.945.613-4.129.539c-2.165-.074-4.449-.233-4.799-2.891a5.499 5.499 0 0 1-.048-.745s2.125.52 4.817.643c1.646.075 3.19-.097 4.758-.283 3.007-.359 5.625-2.212 5.954-3.905.517-2.665.475-6.507.475-6.507zm-4.024 6.709h-2.497V8.469c0-1.29-.543-1.944-1.628-1.944-1.2 0-1.802.776-1.802 2.312v3.349h-2.483v-3.35c0-1.536-.602-2.312-1.802-2.312-1.085 0-1.628.655-1.628 1.944v6.119H4.832V8.284c0-1.289.328-2.313.987-3.07.68-.758 1.569-1.146 2.674-1.146 1.278 0 2.246.491 2.886 1.474L12 6.585l.622-1.043c.64-.983 1.608-1.474 2.886-1.474 1.104 0 1.994.388 2.674 1.146.658.757.986 1.781.986 3.07v6.304z"/></svg><span class="wp-block-social-link-label screen-reader-text">Mastodon</span></a></li>

<li class="wp-social-link wp-social-link-twitter  wp-block-social-link"><a href="https://x.com/westonruter/status/2013491721040965991" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M22.23,5.924c-0.736,0.326-1.527,0.547-2.357,0.646c0.847-0.508,1.498-1.312,1.804-2.27 c-0.793,0.47-1.671,0.812-2.606,0.996C18.324,4.498,17.257,4,16.077,4c-2.266,0-4.103,1.837-4.103,4.103 c0,0.322,0.036,0.635,0.106,0.935C8.67,8.867,5.647,7.234,3.623,4.751C3.27,5.357,3.067,6.062,3.067,6.814 c0,1.424,0.724,2.679,1.825,3.415c-0.673-0.021-1.305-0.206-1.859-0.513c0,0.017,0,0.034,0,0.052c0,1.988,1.414,3.647,3.292,4.023 c-0.344,0.094-0.707,0.144-1.081,0.144c-0.264,0-0.521-0.026-0.772-0.074c0.522,1.63,2.038,2.816,3.833,2.85 c-1.404,1.1-3.174,1.756-5.096,1.756c-0.331,0-0.658-0.019-0.979-0.057c1.816,1.164,3.973,1.843,6.29,1.843 c7.547,0,11.675-6.252,11.675-11.675c0-0.178-0.004-0.355-0.012-0.531C20.985,7.47,21.68,6.747,22.23,5.924z"></path></svg><span class="wp-block-social-link-label screen-reader-text">Twitter</span></a></li></ul>



<p></p>
<p>The post <a href="https://weston.ruter.net/2026/01/19/post-date-block-published-modified/">Post Date Block: Published &amp; Modified</a> appeared first on <a href="https://weston.ruter.net">Weston Ruter</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://weston.ruter.net/2026/01/19/post-date-block-published-modified/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">38580</post-id>	</item>
		<item>
		<title>Would Jesus have found refuge in the United States?</title>
		<link>https://weston.ruter.net/2025/12/31/would-jesus-have-found-refuge-in-the-united-states/</link>
					<comments>https://weston.ruter.net/2025/12/31/would-jesus-have-found-refuge-in-the-united-states/#respond</comments>
		
		<dc:creator><![CDATA[Weston Ruter]]></dc:creator>
		<pubDate>Thu, 01 Jan 2026 07:31:49 +0000</pubDate>
				<category><![CDATA[Current Events]]></category>
		<category><![CDATA[Theology]]></category>
		<guid isPermaLink="false">https://weston.ruter.net/?p=38421</guid>

					<description><![CDATA[<p>When Mary and Joseph fled Judea to protect Jesus from Herod, would they have been welcomed as refugees in America if he had been born today?</p>
<p>The post <a href="https://weston.ruter.net/2025/12/31/would-jesus-have-found-refuge-in-the-united-states/">Would Jesus have found refuge in the United States?</a> appeared first on <a href="https://weston.ruter.net">Weston Ruter</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>This Christmas I&#8217;ve kept thinking about how Mary and Joseph had to flee Judea. They found refuge in Egypt in order to protect the young Jesus from autocratic government persecution:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>An angel of the Lord appeared to Joseph in a dream and said, “Rise, take the child and his mother, and flee to Egypt, and remain there until I tell you, for Herod is about to search for the child, to destroy him.” And he rose and took the child and his mother by night and departed to Egypt and remained there until the death of Herod.</p>
<cite><a href="https://www.bible.com/bible/59/MAT.2.13-15">Matthew 2:13b-15a (ESV)</a></cite></blockquote>



<p>What if Jesus had been born today instead? Would his family have found refuge in America? (I&#8217;m no immigration lawyer, so I&#8217;ve relied on Gemini to help navigate the complicated system.) Given their dire and urgent situation, they wouldn&#8217;t have been able to apply for an immigrant visa through the Green Card Lottery, which is a years-long process. They also wouldn&#8217;t have been able to enter under a refugee program, since that also involves a long process with an extensive vetting pipeline before they would have been possibly admitted as refugees. They also wouldn&#8217;t have been able to use Humanitarian Parole, since that is also a slow process. So for legal entry, a tourist visa seems like the only viable pathway.</p>



<p>If they were modern Israeli citizens, there is the Electronic System for Travel Authorization (ESTA) which allows for approval within 72 hours.</p>



<p>On the other hand, if they were Palestinians (as Bethlehem is in Palestine), there is no such 72-hour approval for flight. They&#8217;d have to get a visa interview. But, because of <a href="https://travel.state.gov/content/travel/en/News/Intercountry-Adoption-News/presidential-proclamation-10998-on-restricting-and-limiting-the-.html">Presidential Proclamation 10998</a> (Restricting and Limiting the Entry of Foreign Nationals) which takes effect on January 1st, 2026:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>the United States is suspending or limiting entry and visa issuance to nationals of 39 countries as well as individuals applying using travel documents issued or endorsed by the Palestinian Authority.</p>
</blockquote>



<p>So in this case of being Palestinian Authority passport holders, they&#8217;d effectively be banned.</p>



<p>Let&#8217;s say they were fortunate to have the freedom of travel as Israeli citizens; this still wouldn&#8217;t necessarily help them. If they came as tourists due to the urgency, but then claimed asylum upon entering, they would be arrested and detained. One of the few groups currently being admitted into the USA as refugees is the white Afrikaners from South Africa. Recently, <a href="https://www.nytimes.com/2025/12/26/us/trump-afrikaner-ice.html?unlocked_article_code=1._08.uWEZ.7VXbnbwU468z&amp;smid=url-share">one such Afrikaner</a> flew to the US and then requested asylum. Since he didn&#8217;t go through the refugee program, he was put into handcuffs and sent to a federal detention facility where he has been for months now.</p>



<p>So the only viable option I see for Mary and Joseph is to fly to the US and enter with an ESTA tourist visa under false pretenses. Since their stay in America under a tourist visa would be for an indeterminate length, this is technically visa fraud. Even if they intend to return to Judea once the threat has passed, they wouldn&#8217;t have known how long that would be. Under ESTA, there is a strict 90-day limit on the length of time that the visitor can remain in the USA. We don&#8217;t know how long Herod the Great lived specifically after they escaped, but it was probably at least 90 days or maybe even up to a couple of years. If they fled to America under ESTA, they would have overstayed their tourist visa. They would have become undocumented immigrants. They would be illegal aliens. (Jesus would be a Dreamer.)</p>



<p>The current administration is aggressively pursuing the deportation of undocumented immigrants, but it is also cutting the <em>legal</em> pathways for immigrants (e.g. Temporary Protected Status), making them also at risk for deportation. When the <a href="https://en.wikipedia.org/wiki/Refugee_Act_of_1980">Refugee Act of 1980</a> was passed, there was an initial baseline of 50,000 refugees allowed into the country per year (with the actual number admitted being much higher). Under the current administration, the yearly cap has been reduced to an <strong>all-time low of 7,500 refugees</strong>. Furthermore, by <a href="https://www.federalregister.gov/documents/2025/10/31/2025-19752/presidential-determination-on-refugee-admissions-for-fiscal-year-2026">executive order</a>, most of these are to “primarily be allocated among Afrikaners from South Africa” (as also noted above). So even if Mary and Joseph had fled to a third country and applied with the U.S. Refugee Admissions Program, they would have no feasible chance of being accepted since they are not Afrikaners. These Jews would not be accepted as refugees.</p>



<p>What is particularly striking to me about the dismantling of the refugee program is that it exists today in part because of a modern persecution of Jews by Nazi Germany. In 1939, a German ocean liner named the <a href="https://en.wikipedia.org/wiki/MS_St._Louis">MS <em>St. Louis</em></a> sailed to the Americas with nearly a thousand Jewish refugees on board. It attempted to dock in America but was denied (after first being denied in Cuba and later denied by Canada). The ship was forced to return to Europe where tragically over a quarter of the passengers were <a href="https://encyclopedia.ushmm.org/content/en/article/voyage-of-the-st-louis">killed</a> in the Holocaust. After the war, moral outrage over the Holocaust led to the US <a href="https://www.uscis.gov/about-us/our-history/stories-from-the-archives/refugee-timeline#timeline-desc95441:~:text=1948-,Displaced%20Persons%20Act%20of%201948,-The%20Displaced%20Persons">passing</a> the Displaced Persons Act of 1948 (with its key amendment in 1950) to allow refugees to enter the country. Three decades later, the more comprehensive Refugee Act of 1980 was <a href="https://www.uscis.gov/about-us/our-history/stories-from-the-archives/refugee-timeline#timeline-desc95441:~:text=1980-,Refugee%20Act%20of%201980,-Over%20a%20decade">passed</a>:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>The Act created a uniform and comprehensive policy to proactively address refugee admissions by:</p>



<ul class="wp-block-list">
<li>Removing the geographic and ideological limits on the definition of “refugee” that had been introduced by the 1965 Amendments to the INA. The new law formally adopted the United Nations’ definition.</li>



<li>Providing the first statutory basis for asylum.</li>



<li>Increasing the number of refugees who could be admitted annually.</li>



<li>Creating the Office of Refugee Resettlement to oversee resettlement programs.</li>
</ul>



<p>Under the Act, the president, in consultation with Congress, sets the annual number of refugee admissions and the allocation of these admissions to refugees coming from various parts of [the] world.</p>
</blockquote>



<p>According to former Deputy Secretary of State William J. Burns, tragedies like that of the MS <em>St. Louis</em> prompted the international community to take action to protect refugees. <a href="https://2009-2017.state.gov/s/d/former/burns/remarks/2012/198190.htm#:~:text=A%20photo%20of%20the%20M.S.%20Saint%20Louis%20hangs%20in%20the%20front%20office%20of%20the%20State%20Department%E2%80%99s%20refugee%20bureau%20as%20a%20powerful%20reminder%20and%20source%20of%20motivation.%20Our%20actions%20since%20the%20Saint%20Louis%2C%20we%20hope%2C%20speak%20more%20eloquently%20than%20any%20words%20could%20to%20our%20dedication%20and%20commitment%20to%20shelter%20and%20to%20protect.">Additionally</a>:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>A photo of the M.S. Saint Louis hangs in the front office of the State Department’s refugee bureau as a powerful reminder and source of motivation. Our actions since the <em>Saint Louis</em>, we hope, speak more eloquently than any words could to our dedication and commitment to shelter and to protect.</p>
</blockquote>



<p>However, it seems our dedication and commitment in the United States is dwindling. I asked Gemini to create a graph of the actual number of refugees admitted per year since 1948:</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bcdfa1510&quot;}" data-wp-interactive="core/image" data-wp-key="6975bcdfa1510" class="wp-block-image alignwide size-large wp-lightbox-container"><img loading="lazy" decoding="async" width="1008" height="504" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 1340px) 100vw, 1340px" src="https://weston.ruter.net/wp-content/uploads/2025/12/actual-us-refugee-admissions-graph.svg" alt="This graph shows the actual number of refugees admitted to the United States each year from 1948 to 2025.

This graph represents the actual families who arrived and were resettled in American communities.

Key Observations in the Arrival Data:

• The Early Spikes (1948–1952): Following the Displaced Persons Act, the U.S. saw a massive influx of nearly 400,000 people fleeing post-war Europe.

• The 1975 &amp; 1980 Peaks: These represent the two largest humanitarian responses in modern U.S. history.
  • 1975: The fall of Saigon led to the immediate evacuation and resettlement of over 130,000 Vietnamese refugees.
  • 1980: This remains the historical peak, driven by the arrival of the &quot;boat people&quot; from Southeast Asia and the Mariel Boatlift from Cuba.

• The Post-9/11 Collapse (2002): Admissions dropped from ~69,000 to ~27,000 as the entire system was frozen to implement the Department of Homeland Security's new security vetting protocols.

• The 2021 Record Low: A combination of the pandemic and significant policy shifts led to the lowest admission number (11,450) since the standardized program was created.

• The 2024 Recovery &amp; 2025 Drop: 2024 saw a major push to hit the 100,000 mark for the first time in 30 years. However, based on first-quarter data and the indefinite suspension of the program in January 2025, arrivals for this year have dropped back down to an estimated 27,500.

• 2026: The cap is an all time low of 7,500.

(Provided by Gemini 3 Flash)" class="wp-image-38435"/><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<details class="wp-block-details has-small-font-size is-layout-flow wp-block-details-is-layout-flow"><summary>Source code</summary><pre class="wp-block-code"><span><code class="hljs language-python"><span class="hljs-keyword">import</span> pandas <span class="hljs-keyword">as</span> pd
<span class="hljs-keyword">import</span> matplotlib.pyplot <span class="hljs-keyword">as</span> plt

<span class="hljs-comment"># Data Construction based on search results and historical summaries</span>
data = {
    <span class="hljs-comment"># Early Acts and Ad-hoc (estimates and rounded historical totals)</span>
    <span class="hljs-number">1948</span>: <span class="hljs-number">813</span>, <span class="hljs-number">1949</span>: <span class="hljs-number">100000</span>, <span class="hljs-number">1950</span>: <span class="hljs-number">100000</span>, <span class="hljs-number">1951</span>: <span class="hljs-number">100000</span>, <span class="hljs-number">1952</span>: <span class="hljs-number">93000</span>,
    <span class="hljs-number">1953</span>: <span class="hljs-number">53000</span>, <span class="hljs-number">1954</span>: <span class="hljs-number">53000</span>, <span class="hljs-number">1955</span>: <span class="hljs-number">53000</span>, <span class="hljs-number">1956</span>: <span class="hljs-number">55000</span>, <span class="hljs-comment"># Hungarian Revolution surge</span>
    <span class="hljs-number">1957</span>: <span class="hljs-number">56000</span>, <span class="hljs-number">1958</span>: <span class="hljs-number">15000</span>, <span class="hljs-number">1959</span>: <span class="hljs-number">20000</span>, <span class="hljs-number">1960</span>: <span class="hljs-number">25000</span>, <span class="hljs-comment"># Start of Cuban wave</span>
    <span class="hljs-number">1961</span>: <span class="hljs-number">50000</span>, <span class="hljs-number">1962</span>: <span class="hljs-number">55000</span>, <span class="hljs-number">1963</span>: <span class="hljs-number">50000</span>, <span class="hljs-number">1964</span>: <span class="hljs-number">45000</span>, <span class="hljs-number">1965</span>: <span class="hljs-number">40000</span>, <span class="hljs-comment"># Freedom Flights / Cuban program</span>
    <span class="hljs-number">1966</span>: <span class="hljs-number">30000</span>, <span class="hljs-number">1967</span>: <span class="hljs-number">35000</span>, <span class="hljs-number">1968</span>: <span class="hljs-number">40000</span>, <span class="hljs-number">1969</span>: <span class="hljs-number">45000</span>, <span class="hljs-number">1970</span>: <span class="hljs-number">48000</span>,
    <span class="hljs-number">1971</span>: <span class="hljs-number">45000</span>, <span class="hljs-number">1972</span>: <span class="hljs-number">40000</span>, <span class="hljs-number">1973</span>: <span class="hljs-number">35000</span>, <span class="hljs-number">1974</span>: <span class="hljs-number">30000</span>,
    <span class="hljs-comment"># Standardized Reporting era starts with 1975 data from RPC</span>
    <span class="hljs-number">1975</span>: <span class="hljs-number">146304</span>, <span class="hljs-number">1976</span>: <span class="hljs-number">27206</span>, <span class="hljs-number">1977</span>: <span class="hljs-number">19946</span>, <span class="hljs-number">1978</span>: <span class="hljs-number">36507</span>, <span class="hljs-number">1979</span>: <span class="hljs-number">111363</span>,
    <span class="hljs-comment"># Official DHS Yearbook Table 13 (Refugee Act of 1980 era)</span>
    <span class="hljs-number">1980</span>: <span class="hljs-number">207120</span>, <span class="hljs-number">1981</span>: <span class="hljs-number">159250</span>, <span class="hljs-number">1982</span>: <span class="hljs-number">98100</span>, <span class="hljs-number">1983</span>: <span class="hljs-number">61220</span>, <span class="hljs-number">1984</span>: <span class="hljs-number">70390</span>, <span class="hljs-number">1985</span>: <span class="hljs-number">67700</span>,
    <span class="hljs-number">1986</span>: <span class="hljs-number">62150</span>, <span class="hljs-number">1987</span>: <span class="hljs-number">64530</span>, <span class="hljs-number">1988</span>: <span class="hljs-number">76480</span>, <span class="hljs-number">1989</span>: <span class="hljs-number">107070</span>, <span class="hljs-number">1990</span>: <span class="hljs-number">122070</span>, <span class="hljs-number">1991</span>: <span class="hljs-number">113390</span>,
    <span class="hljs-number">1992</span>: <span class="hljs-number">115550</span>, <span class="hljs-number">1993</span>: <span class="hljs-number">114180</span>, <span class="hljs-number">1994</span>: <span class="hljs-number">111680</span>, <span class="hljs-number">1995</span>: <span class="hljs-number">98970</span>, <span class="hljs-number">1996</span>: <span class="hljs-number">75420</span>, <span class="hljs-number">1997</span>: <span class="hljs-number">69650</span>,
    <span class="hljs-number">1998</span>: <span class="hljs-number">76710</span>, <span class="hljs-number">1999</span>: <span class="hljs-number">85290</span>, <span class="hljs-number">2000</span>: <span class="hljs-number">72170</span>, <span class="hljs-number">2001</span>: <span class="hljs-number">68920</span>, <span class="hljs-number">2002</span>: <span class="hljs-number">26790</span>, <span class="hljs-number">2003</span>: <span class="hljs-number">28290</span>,
    <span class="hljs-number">2004</span>: <span class="hljs-number">52840</span>, <span class="hljs-number">2005</span>: <span class="hljs-number">53740</span>, <span class="hljs-number">2006</span>: <span class="hljs-number">41090</span>, <span class="hljs-number">2007</span>: <span class="hljs-number">48220</span>, <span class="hljs-number">2008</span>: <span class="hljs-number">60110</span>, <span class="hljs-number">2009</span>: <span class="hljs-number">74600</span>,
    <span class="hljs-number">2010</span>: <span class="hljs-number">73290</span>, <span class="hljs-number">2011</span>: <span class="hljs-number">56380</span>, <span class="hljs-number">2012</span>: <span class="hljs-number">58180</span>, <span class="hljs-number">2013</span>: <span class="hljs-number">69910</span>, <span class="hljs-number">2014</span>: <span class="hljs-number">69980</span>, <span class="hljs-number">2015</span>: <span class="hljs-number">69920</span>,
    <span class="hljs-number">2016</span>: <span class="hljs-number">84990</span>, <span class="hljs-number">2017</span>: <span class="hljs-number">53690</span>, <span class="hljs-number">2018</span>: <span class="hljs-number">22410</span>, <span class="hljs-number">2019</span>: <span class="hljs-number">29920</span>, <span class="hljs-number">2020</span>: <span class="hljs-number">11840</span>, <span class="hljs-number">2021</span>: <span class="hljs-number">11450</span>,
    <span class="hljs-number">2022</span>: <span class="hljs-number">25520</span>, <span class="hljs-number">2023</span>: <span class="hljs-number">60050</span>, <span class="hljs-number">2024</span>: <span class="hljs-number">100060</span>,
    <span class="hljs-comment"># 2025 estimate based on Q1 data and suspension in Jan 2025</span>
    <span class="hljs-number">2025</span>: <span class="hljs-number">27500</span>,
    <span class="hljs-comment"># The current refugee cap (allocated primarily to Afrikaners).</span>
    <span class="hljs-number">2026</span>: <span class="hljs-number">7500</span>
}

df = pd.DataFrame(list(data.items()), columns=&#91;<span class="hljs-string">'Year'</span>, <span class="hljs-string">'Admissions'</span>])
df.to_csv(<span class="hljs-string">'actual_refugee_admissions_1948_2025.csv'</span>, index=<span class="hljs-literal">False</span>)

<span class="hljs-comment"># Plotting</span>
plt.figure(figsize=(<span class="hljs-number">14</span>, <span class="hljs-number">7</span>))
plt.plot(df&#91;<span class="hljs-string">'Year'</span>], df&#91;<span class="hljs-string">'Admissions'</span>], marker=<span class="hljs-string">'s'</span>, linestyle=<span class="hljs-string">'-'</span>, color=<span class="hljs-string">'teal'</span>, markersize=<span class="hljs-number">3</span>, label=<span class="hljs-string">'Actual Admissions'</span>)
plt.fill_between(df&#91;<span class="hljs-string">'Year'</span>], df&#91;<span class="hljs-string">'Admissions'</span>], color=<span class="hljs-string">'teal'</span>, alpha=<span class="hljs-number">0.2</span>)

<span class="hljs-comment"># Specific Event Vertical Lines</span>
plt.axvline(x=<span class="hljs-number">1980</span>, color=<span class="hljs-string">'red'</span>, linestyle=<span class="hljs-string">'--'</span>, alpha=<span class="hljs-number">0.5</span>, label=<span class="hljs-string">'Refugee Act of 1980'</span>)
plt.axvline(x=<span class="hljs-number">1975</span>, color=<span class="hljs-string">'orange'</span>, linestyle=<span class="hljs-string">':'</span>, alpha=<span class="hljs-number">0.6</span>, label=<span class="hljs-string">'Fall of Saigon (1975)'</span>)
plt.axvline(x=<span class="hljs-number">2001</span>, color=<span class="hljs-string">'gray'</span>, linestyle=<span class="hljs-string">':'</span>, alpha=<span class="hljs-number">0.6</span>, label=<span class="hljs-string">'9/11 (2001)'</span>)
plt.axvline(x=<span class="hljs-number">2020</span>, color=<span class="hljs-string">'purple'</span>, linestyle=<span class="hljs-string">':'</span>, alpha=<span class="hljs-number">0.6</span>, label=<span class="hljs-string">'COVID-19 (2020)'</span>)

<span class="hljs-comment"># Text Annotations</span>
plt.text(<span class="hljs-number">1948</span>, <span class="hljs-number">-5500</span>, <span class="hljs-string">'DP Act Begins'</span>, horizontalalignment=<span class="hljs-string">'center'</span>)
plt.text(<span class="hljs-number">1980.3</span>, <span class="hljs-number">210000</span>, <span class="hljs-string">'Historical Peak'</span>, horizontalalignment=<span class="hljs-string">'left'</span>, color=<span class="hljs-string">'darkred'</span>, fontweight=<span class="hljs-string">'bold'</span>)
plt.text(<span class="hljs-number">2024</span>, <span class="hljs-number">103000</span>, <span class="hljs-string">'2024 Recovery'</span>, horizontalalignment=<span class="hljs-string">'center'</span>)
plt.text(<span class="hljs-number">2026</span>, <span class="hljs-number">2000</span>, <span class="hljs-string">'Current Cap'</span>, horizontalalignment=<span class="hljs-string">'center'</span>, color=<span class="hljs-string">'red'</span>)

plt.title(<span class="hljs-string">'Actual U.S. Refugee Admissions (1948-2025)'</span>, fontsize=<span class="hljs-number">16</span>)
plt.xlabel(<span class="hljs-string">'Fiscal Year'</span>, fontsize=<span class="hljs-number">12</span>)
plt.ylabel(<span class="hljs-string">'Actual Admissions'</span>, fontsize=<span class="hljs-number">12</span>)
plt.grid(<span class="hljs-literal">True</span>, linestyle=<span class="hljs-string">'--'</span>, alpha=<span class="hljs-number">0.3</span>)
plt.legend()

plt.tight_layout()
plt.savefig(<span class="hljs-string">'actual-us-refugee-admissions-graph.png'</span>)
plt.savefig(<span class="hljs-string">'actual-us-refugee-admissions-graph.svg'</span>, format=<span class="hljs-string">'svg'</span>)
print(df.tail(<span class="hljs-number">15</span>))</code></span></pre></details>



<p>This next year, 2026, is poised to have <strong>the lowest number of refugees welcomed into the United States since World War II</strong>.</p>



<p>When reflecting on the possibility that Jesus was at one time a refugee or an asylum seeker, I can sense personal experience when he depicts a coming reckoning:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Jesus: “I was a stranger and you did not invite me in, I needed clothes and you did not clothe me, I was sick and in prison and you did not look after me.”</p>



<p>The condemned: “Lord, when did we see you hungry or thirsty or a stranger or needing clothes or sick or in prison, and did not help you?”</p>



<p>Jesus: “Truly I tell you, whatever you did not do for one of the least of these, you did not do for me.”</p>
<cite><a href="https://www.bible.com/bible/59/MAT.25.43-45">Matthew 25:43-45 (ESV)</a>, with my reformatting</cite></blockquote>



<p>Instead of taking in the stranger and looking after those in prison, the United States has been seeking out the stranger (foreigner) to put into detention or even prison, as in the case of <a href="https://en.wikipedia.org/wiki/Terrorism_Confinement_Center"><abbr title="Centro de Confinamiento del Terrorismo">CECOT</abbr></a>. Even immigrants who had come here legally have had their legal status revoked so they can be detained and deported: <a href="https://www.npr.org/2025/12/23/g-s1-103001/trump-immigration-deportation-migration-legal-status">1.6 million people lost legal right to stay in the U.S. in 2025</a>.</p>



<p>The Hebrew Bible (aka the Old Testament) has many admonitions for the Israelites to care for foreigners, for example:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>When a foreigner resides among you in your land, do not mistreat them. The foreigner residing among you must be treated as your native-born. Love them as yourself, for you were foreigners in Egypt.</p>
<cite><a href="https://www.bible.com/bible/111/LEV.19.33-34">Leviticus 19:33-34a (NIV)</a></cite></blockquote>



<p>This sounds an awful lot like due process; it also sounds like an admonition to <strong>treat foreigners with dignity as human beings, not to denigrate them as animals</strong>. The scriptural commands for how to treat foreigners justly because they were foreigners in Egypt also circle back to Jesus. An Old Testament prophecy from <a href="https://www.bible.com/bible/59/HOS.11.1.ESV">Hosea 11:1</a> is attributed to him in <a href="https://www.bible.com/bible/59/MAT.2.15.ESV">Matthew 2:15</a>: “Out of Egypt I called my son.” I find it interesting to think about the prior scriptural commands to care for the foreigner when thinking about Jesus&#8217;s future experience as a refugee in Egypt; it makes me think about God perhaps establishing a regional norm so that his Son would be protected when he was a refugee. <strong>Jesus also identifies with the foreigner and he takes it personally the way they are treated (or mistreated).</strong></p>



<p>When thinking about undocumented immigrants who live in our communities, I also think about whom Jesus identifies as our neighbors, those we are to love as ourselves. They&#8217;re not just fellow citizens (neighbors) of our own country, people who are just like us. In <a href="https://www.bible.com/bible/59/luk.10.25-37">Luke 10:25-37</a>, he tells a story of how a man was robbed and left for dead. The man&#8217;s fellow countrymen passed him by and didn&#8217;t offer any help. But when a foreigner (even a despised Samaritan) came by, the foreigner had compassion on the beaten man and cared for him. Jesus said we are to be a good neighbor like that foreigner.</p>



<p>In the current struggle against antisemitism, I find it inconsistent that refugee laws passed in part to rectify how the United States failed to protect Jewish refugees during the Holocaust are now being suppressed, even as many espouse America as being a “Christian nation”. And this Christmas season, this is brought all the more into focus when thinking about how Jesus was once a refugee and that <strong>he likely would not have found refuge in America today</strong>.</p>
<p>The post <a href="https://weston.ruter.net/2025/12/31/would-jesus-have-found-refuge-in-the-united-states/">Would Jesus have found refuge in the United States?</a> appeared first on <a href="https://weston.ruter.net">Weston Ruter</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://weston.ruter.net/2025/12/31/would-jesus-have-found-refuge-in-the-united-states/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">38421</post-id>	</item>
		<item>
		<title>WordPress 6.9 Performance Landings</title>
		<link>https://weston.ruter.net/2025/11/17/wordpress-6-9-performance-landings/</link>
					<comments>https://weston.ruter.net/2025/11/17/wordpress-6-9-performance-landings/#respond</comments>
		
		<dc:creator><![CDATA[Weston Ruter]]></dc:creator>
		<pubDate>Tue, 18 Nov 2025 06:07:21 +0000</pubDate>
				<category><![CDATA[WordPress]]></category>
		<category><![CDATA[performance]]></category>
		<guid isPermaLink="false">https://weston.ruter.net/?p=38085</guid>

					<description><![CDATA[<p>Celebrating the performance improvements in the latest release, and reflecting on the effort to make it happen.</p>
<p>The post <a href="https://weston.ruter.net/2025/11/17/wordpress-6-9-performance-landings/">WordPress 6.9 Performance Landings</a> appeared first on <a href="https://weston.ruter.net">Weston Ruter</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>WordPress 6.9 includes so many performance improvements! Scripts with <code>fetchpriority</code>, increased inline CSS, minified theme styles, on demand block styles in classic themes, the template enhancement output buffer, and much more! Check out the <a href="https://make.wordpress.org/core/2025/11/18/wordpress-6-9-frontend-performance-field-guide/">field guide</a> I just published:</p>



<figure class="wp-block-embed is-type-wp-embed is-provider-make-wordpress-core wp-block-embed-make-wordpress-core"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="lT6UpVF8O3"><a href="https://make.wordpress.org/core/2025/11/18/wordpress-6-9-frontend-performance-field-guide/">WordPress 6.9 Frontend Performance Field Guide</a></blockquote><iframe loading="lazy" class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  title="&#8220;WordPress 6.9 Frontend Performance Field Guide&#8221; &#8212; Make WordPress Core" src="https://make.wordpress.org/core/2025/11/18/wordpress-6-9-frontend-performance-field-guide/embed/#?secret=KJSo4nstK2#?secret=lT6UpVF8O3" data-secret="lT6UpVF8O3" width="500" height="282" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>



<p>There were many contributors from the <a href="https://make.wordpress.org/performance/handbook/about-the-team/">Core Performance Team</a> involved in making this possible. Personally, this is a culmination of a year of work, including a bunch of research and development I <a href="https://weston.ruter.net/2025/08/27/the-site-speed-frontier-with-performance-lab-and-beyond/">wrote about previously</a> and spoke about at WordCamp US:</p>



<figure class="wp-block-embed is-type-wp-embed is-provider-weston-ruter wp-block-embed-weston-ruter"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="xw4dOfjbci"><a href="https://weston.ruter.net/2025/08/27/the-site-speed-frontier-with-performance-lab-and-beyond/">The Site Speed Frontier with Performance Lab and Beyond</a></blockquote><iframe loading="lazy" class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  title="&#8220;The Site Speed Frontier with Performance Lab and Beyond&#8221; &#8212; Weston Ruter" src="https://weston.ruter.net/2025/08/27/the-site-speed-frontier-with-performance-lab-and-beyond/embed/#?secret=hxQdlWmFSO#?secret=xw4dOfjbci" data-secret="xw4dOfjbci" width="500" height="282" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>



<p>I&#8217;ve been working nights and weekends (and days) to help make sure all of this lands in time for the December 2nd release. This is evident from looking at the <a href="https://make.wordpress.org/updates/2025/11/15/a-month-in-core-october-2025/">“A Month in Core” post for October</a>! It has been fun—feeling like the <a href="https://weston.ruter.net/2025/05/14/a-decade-as-a-core-committer-my-wordpress-contribution-history/">good ol&#8217; contribution days</a> when I was deep in the Customizer up through <a href="https://weston.ruter.net/2017/11/03/wordpress-4-9/">WordPress 4.9</a> (2013–2017). But I&#8217;m really looking forward to being able to unwind for the holidays.</p>



<p><em>I hope your sites get a great speed boost for Christmas!</em> <img src="https://weston.ruter.net/wp-content/plugins/local-twemoji/images/emoji/72x72/1f384.png?ver=17-0-2-2" alt="🎄" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="has-medium-font-size">Where I&#8217;ve shared the field guide:</p>



<ul class="wp-block-social-links is-layout-flex wp-block-social-links-is-layout-flex"><li class="wp-social-link wp-social-link-linkedin  wp-block-social-link"><a href="https://www.linkedin.com/posts/westonruter_wordpress-69-frontend-performance-field-activity-7396418643473002498-7XFC" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M19.7,3H4.3C3.582,3,3,3.582,3,4.3v15.4C3,20.418,3.582,21,4.3,21h15.4c0.718,0,1.3-0.582,1.3-1.3V4.3 C21,3.582,20.418,3,19.7,3z M8.339,18.338H5.667v-8.59h2.672V18.338z M7.004,8.574c-0.857,0-1.549-0.694-1.549-1.548 c0-0.855,0.691-1.548,1.549-1.548c0.854,0,1.547,0.694,1.547,1.548C8.551,7.881,7.858,8.574,7.004,8.574z M18.339,18.338h-2.669 v-4.177c0-0.996-0.017-2.278-1.387-2.278c-1.389,0-1.601,1.086-1.601,2.206v4.249h-2.667v-8.59h2.559v1.174h0.037 c0.356-0.675,1.227-1.387,2.526-1.387c2.703,0,3.203,1.779,3.203,4.092V18.338z"></path></svg><span class="wp-block-social-link-label screen-reader-text">LinkedIn</span></a></li>

<li class="wp-social-link wp-social-link-bluesky  wp-block-social-link"><a href="https://bsky.app/profile/weston.ruter.net/post/3m5uyyujouk2o" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M6.3,4.2c2.3,1.7,4.8,5.3,5.7,7.2.9-1.9,3.4-5.4,5.7-7.2,1.7-1.3,4.3-2.2,4.3.9s-.4,5.2-.6,5.9c-.7,2.6-3.3,3.2-5.6,2.8,4,.7,5.1,3,2.9,5.3-5,5.2-6.7-2.8-6.7-2.8,0,0-1.7,8-6.7,2.8-2.2-2.3-1.2-4.6,2.9-5.3-2.3.4-4.9-.3-5.6-2.8-.2-.7-.6-5.3-.6-5.9,0-3.1,2.7-2.1,4.3-.9h0Z"></path></svg><span class="wp-block-social-link-label screen-reader-text">Bluesky</span></a></li>

<li class="wp-social-link wp-social-link-threads  wp-block-social-link"><a href="https://www.threads.com/@westonruter/post/DRL7SpBjjlb" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M16.3 11.3c-.1 0-.2-.1-.2-.1-.1-2.6-1.5-4-3.9-4-1.4 0-2.6.6-3.3 1.7l1.3.9c.5-.8 1.4-1 2-1 .8 0 1.4.2 1.7.7.3.3.5.8.5 1.3-.7-.1-1.4-.2-2.2-.1-2.2.1-3.7 1.4-3.6 3.2 0 .9.5 1.7 1.3 2.2.7.4 1.5.6 2.4.6 1.2-.1 2.1-.5 2.7-1.3.5-.6.8-1.4.9-2.4.6.3 1 .8 1.2 1.3.4.9.4 2.4-.8 3.6-1.1 1.1-2.3 1.5-4.3 1.5-2.1 0-3.8-.7-4.8-2S5.7 14.3 5.7 12c0-2.3.5-4.1 1.5-5.4 1.1-1.3 2.7-2 4.8-2 2.2 0 3.8.7 4.9 2 .5.7.9 1.5 1.2 2.5l1.5-.4c-.3-1.2-.8-2.2-1.5-3.1-1.3-1.7-3.3-2.6-6-2.6-2.6 0-4.7.9-6 2.6C4.9 7.2 4.3 9.3 4.3 12s.6 4.8 1.9 6.4c1.4 1.7 3.4 2.6 6 2.6 2.3 0 4-.6 5.3-2 1.8-1.8 1.7-4 1.1-5.4-.4-.9-1.2-1.7-2.3-2.3zm-4 3.8c-1 .1-2-.4-2-1.3 0-.7.5-1.5 2.1-1.6h.5c.6 0 1.1.1 1.6.2-.2 2.3-1.3 2.7-2.2 2.7z"/></svg><span class="wp-block-social-link-label screen-reader-text">Threads</span></a></li>

<li class="wp-social-link wp-social-link-twitter  wp-block-social-link"><a href="https://x.com/westonruter/status/1990653489740194222" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M22.23,5.924c-0.736,0.326-1.527,0.547-2.357,0.646c0.847-0.508,1.498-1.312,1.804-2.27 c-0.793,0.47-1.671,0.812-2.606,0.996C18.324,4.498,17.257,4,16.077,4c-2.266,0-4.103,1.837-4.103,4.103 c0,0.322,0.036,0.635,0.106,0.935C8.67,8.867,5.647,7.234,3.623,4.751C3.27,5.357,3.067,6.062,3.067,6.814 c0,1.424,0.724,2.679,1.825,3.415c-0.673-0.021-1.305-0.206-1.859-0.513c0,0.017,0,0.034,0,0.052c0,1.988,1.414,3.647,3.292,4.023 c-0.344,0.094-0.707,0.144-1.081,0.144c-0.264,0-0.521-0.026-0.772-0.074c0.522,1.63,2.038,2.816,3.833,2.85 c-1.404,1.1-3.174,1.756-5.096,1.756c-0.331,0-0.658-0.019-0.979-0.057c1.816,1.164,3.973,1.843,6.29,1.843 c7.547,0,11.675-6.252,11.675-11.675c0-0.178-0.004-0.355-0.012-0.531C20.985,7.47,21.68,6.747,22.23,5.924z"></path></svg><span class="wp-block-social-link-label screen-reader-text">Twitter</span></a></li>

<li class="wp-social-link wp-social-link-mastodon  wp-block-social-link"><a href="https://mastodon.social/@westonruter/115569037834231729" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M23.193 7.879c0-5.206-3.411-6.732-3.411-6.732C18.062.357 15.108.025 12.041 0h-.076c-3.068.025-6.02.357-7.74 1.147 0 0-3.411 1.526-3.411 6.732 0 1.192-.023 2.618.015 4.129.124 5.092.934 10.109 5.641 11.355 2.17.574 4.034.695 5.535.612 2.722-.15 4.25-.972 4.25-.972l-.09-1.975s-1.945.613-4.129.539c-2.165-.074-4.449-.233-4.799-2.891a5.499 5.499 0 0 1-.048-.745s2.125.52 4.817.643c1.646.075 3.19-.097 4.758-.283 3.007-.359 5.625-2.212 5.954-3.905.517-2.665.475-6.507.475-6.507zm-4.024 6.709h-2.497V8.469c0-1.29-.543-1.944-1.628-1.944-1.2 0-1.802.776-1.802 2.312v3.349h-2.483v-3.35c0-1.536-.602-2.312-1.802-2.312-1.085 0-1.628.655-1.628 1.944v6.119H4.832V8.284c0-1.289.328-2.313.987-3.07.68-.758 1.569-1.146 2.674-1.146 1.278 0 2.246.491 2.886 1.474L12 6.585l.622-1.043c.64-.983 1.608-1.474 2.886-1.474 1.104 0 1.994.388 2.674 1.146.658.757.986 1.781.986 3.07v6.304z"/></svg><span class="wp-block-social-link-label screen-reader-text">Mastodon</span></a></li></ul>
<p>The post <a href="https://weston.ruter.net/2025/11/17/wordpress-6-9-performance-landings/">WordPress 6.9 Performance Landings</a> appeared first on <a href="https://weston.ruter.net">Weston Ruter</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://weston.ruter.net/2025/11/17/wordpress-6-9-performance-landings/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">38085</post-id>	</item>
		<item>
		<title>Joining WP Engine</title>
		<link>https://weston.ruter.net/2025/08/27/joining-wp-engine/</link>
					<comments>https://weston.ruter.net/2025/08/27/joining-wp-engine/#comments</comments>
		
		<dc:creator><![CDATA[Weston Ruter]]></dc:creator>
		<pubDate>Wed, 27 Aug 2025 22:32:35 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://weston.ruter.net/?p=36249</guid>

					<description><![CDATA[<p>I've joined WP Engine to contribute full time to WordPress core, continuing the performance work I started while at Google.</p>
<p>The post <a href="https://weston.ruter.net/2025/08/27/joining-wp-engine/">Joining WP Engine</a> appeared first on <a href="https://weston.ruter.net">Weston Ruter</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>As I mentioned in my post <a href="https://weston.ruter.net/2025/05/14/a-decade-as-a-core-committer-my-wordpress-contribution-history/">A Decade as a Core Committer: My WordPress Contribution History</a>, my role at Google was sadly eliminated in April. When I was <a href="https://weston.ruter.net/2018/09/19/becoming-a-googler/">hired at Google</a> back in 2018, I was brought on as a WordPress contributor to be part of a “scaled activation”&nbsp;team to make the web better at scale through WordPress, since it is the largest percentage of the web by far. It was a dream job in many ways. Not only was I primarily sponsored to make the web better, I also just loved everything about working from the <a href="https://www.google.com/about/careers/applications/locations/portland/">Google PDX office</a>. For the past couple years at Google, my work was focused on the Core Performance Team, dedicated to making WordPress fast and improving the user experience. With that chapter at Google coming to a close, I was eager to keep the momentum going by finding a new position that would allow me to continue contributing full time.</p>



<p>In what may be surprising to many, I&#8217;ve found this role at <strong>WP Engine</strong>. I&#8217;ve joined WPE as a principal software engineer to work full time on contributing to WordPress core. I&#8217;ll be continuing the performance work I had been doing and which I have continued to work on while I was between roles, with thanks to <a href="https://github.com/sponsors/westonruter#sponsors-section-list">my sponsors</a>! I&#8217;ll be working from home, as I was well accustomed to during my 8 years at <a href="https://x-team.com/">X-Team</a>/<a href="https://xwp.co/">XWP</a>, and like most of us during COVID. I&#8217;m looking forward to resuming frequent visits to my <a href="https://en.wikipedia.org/wiki/Montavilla,_Portland,_Oregon">Montavilla</a> neighborhood businesses and regular <a href="https://weston.ruter.net/2020/09/30/story-running-on-mount-tabor/">runs up Mount Tabor</a>.</p>



<p>As far as I know, my being hired is not related in any way to the <a href="https://techcrunch.com/2025/01/12/wordpress-vs-wp-engine-drama-explained/">current WP drama</a>. I&#8217;ve tried to stay out of the drama. But WP Engine reached out to me, and after many conversations, I was encouraged with the role they were looking to fill: exactly what I had been looking for when I was let go by Google. There are also a couple former fellow XWPeople who are now at WPE who I&#8217;m looking forward to working with again!</p>



<p>I attribute WP Engine&#8217;s bringing me on board as a testament to their core value to be <a href="https://wpengine.com/blog/honoring-our-past-with-an-eye-towards-the-future-wp-engines-core-values/#committed">committed to give back</a>:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>We advance the WordPress Community through original contribution, sharing expertise, and active participation.</p>
</blockquote>



<p>I&#8217;m looking forward to continuing my WordPress core contribution journey now at a company fully immersed and invested in WordPress!</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="has-medium-font-size">Where I&#8217;ve posted this:</p>



<ul class="wp-block-social-links is-layout-flex wp-block-social-links-is-layout-flex"><li class="wp-social-link wp-social-link-linkedin  wp-block-social-link"><a href="https://www.linkedin.com/posts/westonruter_joining-wp-engine-weston-ruter-activity-7366600163735334916-kz24?utm_source=share&#038;utm_medium=member_desktop&#038;rcm=ACoAAACIeJ0BUsdEu-G5aiGg1JkXrMQ-C6tbCsI" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M19.7,3H4.3C3.582,3,3,3.582,3,4.3v15.4C3,20.418,3.582,21,4.3,21h15.4c0.718,0,1.3-0.582,1.3-1.3V4.3 C21,3.582,20.418,3,19.7,3z M8.339,18.338H5.667v-8.59h2.672V18.338z M7.004,8.574c-0.857,0-1.549-0.694-1.549-1.548 c0-0.855,0.691-1.548,1.549-1.548c0.854,0,1.547,0.694,1.547,1.548C8.551,7.881,7.858,8.574,7.004,8.574z M18.339,18.338h-2.669 v-4.177c0-0.996-0.017-2.278-1.387-2.278c-1.389,0-1.601,1.086-1.601,2.206v4.249h-2.667v-8.59h2.559v1.174h0.037 c0.356-0.675,1.227-1.387,2.526-1.387c2.703,0,3.203,1.779,3.203,4.092V18.338z"></path></svg><span class="wp-block-social-link-label screen-reader-text">LinkedIn</span></a></li>

<li class="wp-social-link wp-social-link-bluesky  wp-block-social-link"><a href="https://bsky.app/profile/weston.ruter.net/post/3lxg46lfq7c2c" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M6.3,4.2c2.3,1.7,4.8,5.3,5.7,7.2.9-1.9,3.4-5.4,5.7-7.2,1.7-1.3,4.3-2.2,4.3.9s-.4,5.2-.6,5.9c-.7,2.6-3.3,3.2-5.6,2.8,4,.7,5.1,3,2.9,5.3-5,5.2-6.7-2.8-6.7-2.8,0,0-1.7,8-6.7,2.8-2.2-2.3-1.2-4.6,2.9-5.3-2.3.4-4.9-.3-5.6-2.8-.2-.7-.6-5.3-.6-5.9,0-3.1,2.7-2.1,4.3-.9h0Z"></path></svg><span class="wp-block-social-link-label screen-reader-text">Bluesky</span></a></li>

<li class="wp-social-link wp-social-link-twitter  wp-block-social-link"><a href="https://x.com/westonruter/status/1960835801358721258" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M22.23,5.924c-0.736,0.326-1.527,0.547-2.357,0.646c0.847-0.508,1.498-1.312,1.804-2.27 c-0.793,0.47-1.671,0.812-2.606,0.996C18.324,4.498,17.257,4,16.077,4c-2.266,0-4.103,1.837-4.103,4.103 c0,0.322,0.036,0.635,0.106,0.935C8.67,8.867,5.647,7.234,3.623,4.751C3.27,5.357,3.067,6.062,3.067,6.814 c0,1.424,0.724,2.679,1.825,3.415c-0.673-0.021-1.305-0.206-1.859-0.513c0,0.017,0,0.034,0,0.052c0,1.988,1.414,3.647,3.292,4.023 c-0.344,0.094-0.707,0.144-1.081,0.144c-0.264,0-0.521-0.026-0.772-0.074c0.522,1.63,2.038,2.816,3.833,2.85 c-1.404,1.1-3.174,1.756-5.096,1.756c-0.331,0-0.658-0.019-0.979-0.057c1.816,1.164,3.973,1.843,6.29,1.843 c7.547,0,11.675-6.252,11.675-11.675c0-0.178-0.004-0.355-0.012-0.531C20.985,7.47,21.68,6.747,22.23,5.924z"></path></svg><span class="wp-block-social-link-label screen-reader-text">Twitter</span></a></li>

<li class="wp-social-link wp-social-link-mastodon  wp-block-social-link"><a href="https://mastodon.social/@westonruter/115103142253665474" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M23.193 7.879c0-5.206-3.411-6.732-3.411-6.732C18.062.357 15.108.025 12.041 0h-.076c-3.068.025-6.02.357-7.74 1.147 0 0-3.411 1.526-3.411 6.732 0 1.192-.023 2.618.015 4.129.124 5.092.934 10.109 5.641 11.355 2.17.574 4.034.695 5.535.612 2.722-.15 4.25-.972 4.25-.972l-.09-1.975s-1.945.613-4.129.539c-2.165-.074-4.449-.233-4.799-2.891a5.499 5.499 0 0 1-.048-.745s2.125.52 4.817.643c1.646.075 3.19-.097 4.758-.283 3.007-.359 5.625-2.212 5.954-3.905.517-2.665.475-6.507.475-6.507zm-4.024 6.709h-2.497V8.469c0-1.29-.543-1.944-1.628-1.944-1.2 0-1.802.776-1.802 2.312v3.349h-2.483v-3.35c0-1.536-.602-2.312-1.802-2.312-1.085 0-1.628.655-1.628 1.944v6.119H4.832V8.284c0-1.289.328-2.313.987-3.07.68-.758 1.569-1.146 2.674-1.146 1.278 0 2.246.491 2.886 1.474L12 6.585l.622-1.043c.64-.983 1.608-1.474 2.886-1.474 1.104 0 1.994.388 2.674 1.146.658.757.986 1.781.986 3.07v6.304z"/></svg><span class="wp-block-social-link-label screen-reader-text">Mastodon</span></a></li>

<li class="wp-social-link wp-social-link-threads  wp-block-social-link"><a href="https://www.threads.com/@westonruter/post/DN4CoN2Esn7?xmt=AQF0yJplskrfY6oHVh4vRlE3DV_wKvinkt72VC0J-SlKYw" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M16.3 11.3c-.1 0-.2-.1-.2-.1-.1-2.6-1.5-4-3.9-4-1.4 0-2.6.6-3.3 1.7l1.3.9c.5-.8 1.4-1 2-1 .8 0 1.4.2 1.7.7.3.3.5.8.5 1.3-.7-.1-1.4-.2-2.2-.1-2.2.1-3.7 1.4-3.6 3.2 0 .9.5 1.7 1.3 2.2.7.4 1.5.6 2.4.6 1.2-.1 2.1-.5 2.7-1.3.5-.6.8-1.4.9-2.4.6.3 1 .8 1.2 1.3.4.9.4 2.4-.8 3.6-1.1 1.1-2.3 1.5-4.3 1.5-2.1 0-3.8-.7-4.8-2S5.7 14.3 5.7 12c0-2.3.5-4.1 1.5-5.4 1.1-1.3 2.7-2 4.8-2 2.2 0 3.8.7 4.9 2 .5.7.9 1.5 1.2 2.5l1.5-.4c-.3-1.2-.8-2.2-1.5-3.1-1.3-1.7-3.3-2.6-6-2.6-2.6 0-4.7.9-6 2.6C4.9 7.2 4.3 9.3 4.3 12s.6 4.8 1.9 6.4c1.4 1.7 3.4 2.6 6 2.6 2.3 0 4-.6 5.3-2 1.8-1.8 1.7-4 1.1-5.4-.4-.9-1.2-1.7-2.3-2.3zm-4 3.8c-1 .1-2-.4-2-1.3 0-.7.5-1.5 2.1-1.6h.5c.6 0 1.1.1 1.6.2-.2 2.3-1.3 2.7-2.2 2.7z"/></svg><span class="wp-block-social-link-label screen-reader-text">Threads</span></a></li></ul>



<p></p>
<p>The post <a href="https://weston.ruter.net/2025/08/27/joining-wp-engine/">Joining WP Engine</a> appeared first on <a href="https://weston.ruter.net">Weston Ruter</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://weston.ruter.net/2025/08/27/joining-wp-engine/feed/</wfw:commentRss>
			<slash:comments>7</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">36249</post-id>	</item>
		<item>
		<title>The Site Speed Frontier with Performance Lab and Beyond</title>
		<link>https://weston.ruter.net/2025/08/27/the-site-speed-frontier-with-performance-lab-and-beyond/</link>
					<comments>https://weston.ruter.net/2025/08/27/the-site-speed-frontier-with-performance-lab-and-beyond/#respond</comments>
		
		<dc:creator><![CDATA[Weston Ruter]]></dc:creator>
		<pubDate>Wed, 27 Aug 2025 18:05:49 +0000</pubDate>
				<category><![CDATA[WordPress]]></category>
		<category><![CDATA[performance]]></category>
		<category><![CDATA[talk]]></category>
		<guid isPermaLink="false">https://weston.ruter.net/?p=35813</guid>

					<description><![CDATA[<p>Analysis of the LCP impact for optimizations featured by the Performance Lab plugin and proposed for WordPress core. Presented at WCUS 2025.</p>
<p>The post <a href="https://weston.ruter.net/2025/08/27/the-site-speed-frontier-with-performance-lab-and-beyond/">The Site Speed Frontier with Performance Lab and Beyond</a> appeared first on <a href="https://weston.ruter.net">Weston Ruter</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="has-accent-5-background-color has-background"><strong>Update:</strong> <a href="https://weston.ruter.net/2025/08/27/the-site-speed-frontier-with-performance-lab-and-beyond/#script-module-deprioritization">Script Module Deprioritization</a> and <a href="https://weston.ruter.net/2025/08/27/the-site-speed-frontier-with-performance-lab-and-beyond/#minified-css-inlining">Minified CSS Inlining</a> are both landing in WordPress 6.9, along with many other performance improvements. Check out the <a href="https://make.wordpress.org/core/2025/11/18/wordpress-6-9-frontend-performance-field-guide/">field guide</a>.</p>



<p>At WordCamp US 2025 this year, I&#8217;m presenting a talk called “<a href="https://us.wordcamp.org/2025/session/the-site-speed-frontier-with-performance-lab-and-beyond/">The Site Speed Frontier with Performance Lab and Beyond</a>” with the following description:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>The Core Performance team has been incubating enhancements for WordPress through the Performance Lab plugin. These have been available for a few years now; some have been merged into core (e.g. Speculative Loading) while others are more experimental and remain in testing (e.g. Optimization Detective). This talk will look at how these performance plugins impact the speed of a stock WordPress site running the Twenty Twenty-Five default theme, using Core Web Vitals benchmarks and Lighthouse scores. It will also look at how the theme&#8217;s performance can be further tuned, including the use of core patches proposed for the next major release (also available in plugin form to leverage today) to further accelerate the loading of pages to improve the user experience of site visitors.</p>
</blockquote>



<p>Here&#8217;s my talk <a href="https://youtu.be/VHy_mRe1pek?si=UYzEccemyk4UXNOS">from the WordPress YouTube channel</a> (also <a href="https://wordpress.tv/2025/09/03/the-site-speed-frontier-with-performance-lab-and-beyond/">available on WordPress.tv</a>):</p>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" title="WordCamp US 2025: Weston Ruter &quot;The Site Speed Frontier with Performance Lab and Beyond&quot;" width="500" height="281" src="https://www.youtube.com/embed/VHy_mRe1pek?feature=oembed&#038;rel=0&#038;modestbranding=1" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>



<p>And here are my <a href="https://docs.google.com/presentation/d/1KMnm49qF6F9AIkH7J4iyiiPbXGXZ-iq5fK2MYqLT_YU/edit?usp=sharing">slides</a> as well:</p>



<p><iframe src="https://docs.google.com/presentation/d/e/2PACX-1vR6NCvwPl1mebgqCFTAzafWki0UIj4pDmsdvy5qZif-23TpNUoq8b86dwNzw8eL3re2-c3SOE4alWvN/embed?start=false&#038;loop=false&#038;delayms=3000" frameborder="0" class="intrinsic-ignore" width="876" height="570" style="width:100%; height:auto; aspect-ratio: 876 / 570;" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true" loading="lazy" title="Google Slides deck for my talk"></iframe></p>



<p>And what follows is my talk in blog post form, greatly expanded with a lot more details than I had time to share during my talk.</p>



<p>Table of contents:</p>



<nav aria-label="Table of Contents" class="wp-block-table-of-contents"><ol><li><a class="wp-block-table-of-contents__entry" href="https://weston.ruter.net/2025/08/27/the-site-speed-frontier-with-performance-lab-and-beyond/#performance-lab">Performance Lab</a></li><li><a class="wp-block-table-of-contents__entry" href="https://weston.ruter.net/2025/08/27/the-site-speed-frontier-with-performance-lab-and-beyond/#case-study-twenty-twenty-five">Case Study: Twenty Twenty-Five</a></li><li><a class="wp-block-table-of-contents__entry" href="https://weston.ruter.net/2025/08/27/the-site-speed-frontier-with-performance-lab-and-beyond/#performance-testing-methodology">Performance Testing Methodology</a><ol><li><a class="wp-block-table-of-contents__entry" href="https://weston.ruter.net/2025/08/27/the-site-speed-frontier-with-performance-lab-and-beyond/#largest-contentful-paint">Largest Contentful Paint</a></li><li><a class="wp-block-table-of-contents__entry" href="https://weston.ruter.net/2025/08/27/the-site-speed-frontier-with-performance-lab-and-beyond/#benchmarking">Benchmarking</a></li></ol></li><li><a class="wp-block-table-of-contents__entry" href="https://weston.ruter.net/2025/08/27/the-site-speed-frontier-with-performance-lab-and-beyond/#analyzing-optimization-impact-on-lcp">Analyzing Optimization Impact on LCP</a><ol><li><a class="wp-block-table-of-contents__entry" href="https://weston.ruter.net/2025/08/27/the-site-speed-frontier-with-performance-lab-and-beyond/#image-placeholders">Image Placeholders</a></li><li><a class="wp-block-table-of-contents__entry" href="https://weston.ruter.net/2025/08/27/the-site-speed-frontier-with-performance-lab-and-beyond/#modern-image-formats">Modern Image Formats</a></li><li><a class="wp-block-table-of-contents__entry" href="https://weston.ruter.net/2025/08/27/the-site-speed-frontier-with-performance-lab-and-beyond/#enhanced-responsive-images">Enhanced Responsive Images</a></li><li><a class="wp-block-table-of-contents__entry" href="https://weston.ruter.net/2025/08/27/the-site-speed-frontier-with-performance-lab-and-beyond/#image-prioritizer">Image Prioritizer</a><ol><li><a class="wp-block-table-of-contents__entry" href="https://weston.ruter.net/2025/08/27/the-site-speed-frontier-with-performance-lab-and-beyond/#responsive-image-prioritization">Responsive Image Prioritization</a></li><li><a class="wp-block-table-of-contents__entry" href="https://weston.ruter.net/2025/08/27/the-site-speed-frontier-with-performance-lab-and-beyond/#background-image-prioritization">Background Image Prioritization</a></li></ol></li><li><a class="wp-block-table-of-contents__entry" href="https://weston.ruter.net/2025/08/27/the-site-speed-frontier-with-performance-lab-and-beyond/#speculative-loading">Speculative Loading</a></li><li><a class="wp-block-table-of-contents__entry" href="https://weston.ruter.net/2025/08/27/the-site-speed-frontier-with-performance-lab-and-beyond/#view-transitions">View Transitions</a></li><li><a class="wp-block-table-of-contents__entry" href="https://weston.ruter.net/2025/08/27/the-site-speed-frontier-with-performance-lab-and-beyond/#nocache-bfcache">Instant Back/Forward</a></li><li><a class="wp-block-table-of-contents__entry" href="https://weston.ruter.net/2025/08/27/the-site-speed-frontier-with-performance-lab-and-beyond/#script-module-deprioritization">Script Module Deprioritization</a></li><li><a class="wp-block-table-of-contents__entry" href="https://weston.ruter.net/2025/08/27/the-site-speed-frontier-with-performance-lab-and-beyond/#minified-css-inlining">Minified CSS Inlining</a></li></ol></li><li><a class="wp-block-table-of-contents__entry" href="https://weston.ruter.net/2025/08/27/the-site-speed-frontier-with-performance-lab-and-beyond/#whats-next">What&#8217;s Next</a></li></ol></nav>



<h2 class="wp-block-heading" id="performance-lab">Performance Lab</h2>



<p>I&#8217;ve been a WordPress <a href="https://make.wordpress.org/core/handbook/about/organization/#committers">core committer</a> for <a href="https://weston.ruter.net/2025/05/14/a-decade-as-a-core-committer-my-wordpress-contribution-history/">over 10 years</a>, and since Spring 2023 I&#8217;ve been heavily involved on the <a href="https://make.wordpress.org/performance/handbook/about-the-team/">Core Performance Team</a>. In addition to contributing patches directly to the WordPress core codebase, we also develop new performance optimizations in the form of feature plugins. We use our <a href="https://wordpress.org/plugins/performance-lab/">Performance Lab</a> plugin as a way to collect the feature plugins we&#8217;re currently working on to facilitate discovery:</p>



<figure class="wp-block-embed is-type-wp-embed is-provider-plugin-directory wp-block-embed-plugin-directory"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="w7klbIWdhe"><a href="https://wordpress.org/plugins/performance-lab/">Performance Lab</a></blockquote><iframe loading="lazy" class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  title="&#8220;Performance Lab&#8221; &#8212; Plugin Directory" src="https://wordpress.org/plugins/performance-lab/embed/#?secret=eSqZhL7H1y#?secret=w7klbIWdhe" data-secret="w7klbIWdhe" width="500" height="282" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>



<p>Most of these performance feature plugins are developed in the <a href="https://github.com/wordpress/performance">WordPress/performance</a> monorepo on GitHub. In the same way as the Gutenberg plugin serves as a way to develop new editor features, the Performance Lab plugin is a way we incubate new performance features. It allows us to get feedback from users and test the impact prior to being proposed for merging into a new release of WordPress core when it gets rolled out to <a href="https://w3techs.com/technologies/overview/content_management">~43%</a> of the web.</p>



<h2 class="wp-block-heading" id="case-study-twenty-twenty-five">Case Study: Twenty Twenty-Five</h2>



<p>The default theme for the current version of WordPress is <a href="https://wordpress.org/themes/twentytwentyfive/">Twenty Twenty- Five</a>. Default themes in core basically encapsulate the latest and greatest in what WordPress has to offer in terms of features and performance. Indeed, <a href="https://wordpress.org/documentation/article/block-themes/">block themes</a> are generally faster than classic themes (especially with page caching) for a few reasons, including:</p>



<ul class="wp-block-list">
<li>Scripts and styles are <a href="https://make.wordpress.org/core/2021/07/01/block-styles-loading-enhancements-in-wordpress-5-8/">selectively loaded</a> based on whether their blocks actually used on the page.</li>



<li>Blocks are more likely to use the <a href="https://developer.wordpress.org/block-editor/reference-guides/interactivity-api/">Interactivity API</a> which involves deferred <a href="https://make.wordpress.org/core/2024/03/04/script-modules-in-6-5/">script modules</a> (which don&#8217;t block rendering) and <a href="https://developer.wordpress.org/block-editor/reference-guides/interactivity-api/core-concepts/server-side-rendering/">server-side rendering</a>.</li>
</ul>



<p>So the Twenty Twenty-Five theme should be very fast, and indeed it is. But with Performance Lab features (and beyond), it can be made even faster.</p>



<p>Your mileage will vary with other themes, either having an even greater impact or a lesser one. Every site is unique (hopefully!) and so the impact of optimizations depends on a page&#8217;s contents, how a theme is built, and which plugins are active. But in this post, I&#8217;ll show the impact of the optimizations in various page layouts of the Twenty Twenty-Five Theme.</p>



<h2 class="wp-block-heading" id="performance-testing-methodology">Performance Testing Methodology</h2>



<p>Perhaps the most popular way to analyze the performance of a webpage is to use <a href="https://developer.chrome.com/docs/lighthouse/overview/">Lighthouse</a>, either <a href="https://developer.chrome.com/docs/lighthouse/overview#devtools">in Chrome DevTools</a> or via the bottom half of <a href="https://pagespeed.web.dev/">PageSpeed Insights</a>. Lighthouse allows you to test pages either as a desktop or mobile device, <a href="https://github.com/GoogleChrome/lighthouse#why-is-the-performance-score-so-low-it-looks-fine-to-me">emulating</a> the viewport, CPU, and connection speed. Lighthouse is an important tool to get a sense of a page&#8217;s performance, but it has limitations. It captures data from a single page load on a simulated device. There is often <a href="https://github.com/GoogleChrome/lighthouse?tab=readme-ov-file#why-does-the-performance-score-change-so-much">variability</a> in the results, and it also doesn&#8217;t reflect the experience of real users which is what you&#8217;d get from Real User Monitoring (RUM), such as in the <a href="https://developer.chrome.com/docs/crux">Chrome User Experience Report</a> (CrUX)—shown in the first section of PageSpeed Insights. Lighthouse provides simulated <a href="https://web.dev/articles/lab-and-field-data-differences#lab_data">lab data</a> whereas CrUX provides real <a href="https://web.dev/articles/lab-and-field-data-differences#field_data">field data</a>, which is more accurate. Nevertheless, field data can take a long time to collect and it can be difficult to do A/B tests at scale to capture the before/after performance impacts. That said, CrUX is definitely used to track the performance of new WordPress releases overall, as <a href="https://felix-arntz.me/">Felix Arntz</a> shared the <a href="https://make.wordpress.org/core/2023/12/19/wordpress-performance-impact-on-core-web-vitals-in-2023/">WordPress performance impact on Core Web Vitals in 2023</a>. Felix also wrote up how to <a href="https://make.wordpress.org/core/2024/04/05/conducting-wordpress-performance-research-in-the-field/">conduct WordPress performance research in the field</a>.</p>



<p>For the purposes of measuring the impact of performance optimizations here, lab data will be more practical because the results are available in real time, without having to wait for real users to provide field data. And I&#8217;m interested in relative performance impacts, not necessarily absolute ones. </p>



<p>Running a Lighthouse audit before and after a Performance Lab feature plugin is active is a way to measure the impact of the optimization. However, given variability in the results, it can be difficult to be certain of the improvement. A Lighthouse audit may no longer flag an area for improvement, but the overall Lighthouse score may be unchanged. Indeed, even a Lighthouse score of 100 doesn&#8217;t mean the page performance is “perfect”. As <a href="https://discuss.httparchive.org/t/lighthouse-scores-as-predictors-of-page-level-crux-data/2232#:~:text=Of%20the%20pages%20that%20got%20a%2090%2B%20in%20Lighthouse%20in%20September%2C%2043%25%20didn%E2%80%99t%20meet%20one%20or%20more%20CWV%20threshold.">found</a> by Brendan Kenny:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Of the pages that got a 90+ in Lighthouse in September [2021], 43% didn’t meet one or more CWV threshold.</p>
</blockquote>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bcdfb603c&quot;}" data-wp-interactive="core/image" data-wp-key="6975bcdfb603c" class="wp-block-image aligncenter size-medium wp-lightbox-container"><img data-recalc-dims="1" data-dominant-color="f5f9f6" data-has-transparency="false" style="--dominant-color: #f5f9f6;" loading="lazy" decoding="async" width="300" height="236" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 300px) 100vw, 300px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-performance-score-100.png?resize=300%2C236&#038;ssl=1" alt="Lighthouse performance score of 100" class="wp-image-36044 not-transparent" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-performance-score-100.png?resize=300%2C236&amp;ssl=1 300w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-performance-score-100.png?resize=700%2C551&amp;ssl=1 700w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-performance-score-100.png?resize=768%2C604&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-performance-score-100.png?resize=1536%2C1209&amp;ssl=1 1536w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-performance-score-100.png?resize=150%2C118&amp;ssl=1 150w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-performance-score-100.png?w=2000&amp;ssl=1 2000w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<p>A great score—even 100—doesn&#8217;t mean there still isn&#8217;t a lot of room for improvement!</p>



<h3 class="wp-block-heading" id="largest-contentful-paint">Largest Contentful Paint</h3>



<p>One of the main components in <a href="https://developer.chrome.com/docs/lighthouse/performance/performance-scoring">calculating</a> Lighthouse&#8217;s Performance score is the <a href="https://web.dev/articles/lcp">Largest Contentful Paint</a> (LCP) metric of <a href="https://web.dev/articles/vitals#core-web-vitals">Core Web Vitals</a> (CWV). LCP metric is <a href="https://developer.chrome.com/docs/lighthouse/performance/performance-scoring">weighted</a> at 25% of the total Lighthouse score. As noted in how <a href="https://developer.chrome.com/docs/lighthouse/performance/performance-scoring">how Lighthouse scores are determined</a>:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>The metric value for LCP represents the time duration between the user initiating the page load and the page rendering its primary content. Based on real website data, top-performing sites render LCP in about 1,220ms, so that metric value is mapped to a score of 99.</p>
</blockquote>



<p>A 1.2 second LCP is 12 times slower than a 100 ms LCP, where 100 ms is a <a href="https://www.nngroup.com/articles/response-times-3-important-limits/#:~:text=0.1%20second%20is%20about%20the%20limit%20for%20having%20the%20user%20feel%20that%20the%20system%20is%20reacting%20instantaneously%2C%20meaning%20that%20no%20special%20feedback%20is%20necessary%20except%20to%20display%20the%20result.">proposed</a> threshold for the user to perceive a reaction as being instantaneous. Nevertheless, a “good” LCP value is 2.5 seconds and below:</p>



<figure class="wp-block-image aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="768" height="192" sizes="auto, (max-width: 645px) 100vw, 645px" src="https://weston.ruter.net/wp-content/uploads/2025/08/good-lcp-values.svg" alt="LCP (Largest Contentful Paint). Graph showing Good, Needs Improvement, and Poor divided by 800 ms and 1800 ms thresholds." class="wp-image-35844" style="width:645px;height:auto"/><figcaption class="wp-element-caption">A good LCP value is 2.5 seconds or less. (Courtesy <a href="https://web.dev/articles/lcp">web.dev</a>)</figcaption></figure>



<p>The LCP metric can be further subdivided, with the first part represented by the <a href="https://web.dev/articles/ttfb">Time To First Byte</a> (TTFB) metric. The longer it takes the server to respond with the generated HTML document, the more this will hurt the LCP metric. A slow TTFB means you are less likely to have a good LCP. If a site has a 1.5-second TTFB which needs improvement, then this leaves only 1 second for the LCP element to be rendered to get a good LCP metric. A good TTFB is considered to be about half that, at 800 ms and below:</p>



<figure class="wp-block-image aligncenter size-large is-resized"><img loading="lazy" decoding="async" width="768" height="192" sizes="auto, (max-width: 645px) 100vw, 645px" src="https://weston.ruter.net/wp-content/uploads/2025/08/good-ttfb-values.svg" alt="TTFB (Time To First Byte). Graph showing Good, Needs Improvement, and Poor divided by 800 ms and 1800 ms thresholds." class="wp-image-35845" style="width:645px;height:auto"/><figcaption class="wp-element-caption">Good TTFB values are 0.8 seconds or less, and poor values are greater than 1.8 seconds. (Courtesy <a href="https://web.dev/articles/ttfb">web.dev</a>)</figcaption></figure>



<p>Unfortunately, in looking at HTTP Archive&#8217;s <a href="https://httparchive.org/reports/techreport/landing">Tech Report</a>, as of July 2025, only <a href="https://httparchive.org/reports/techreport/tech?tech=ALL%2CWordPress&amp;geo=ALL&amp;rank=ALL&amp;good-cwv-over-time=TTFB&amp;client=desktop#comparison-good-cwvs">31% of desktop clients</a> visiting WordPress sites experience a good TTFB, whereas it&#8217;s just <a href="https://httparchive.org/reports/techreport/tech?tech=ALL%2CWordPress&amp;geo=ALL&amp;rank=ALL&amp;good-cwv-over-time=TTFB&amp;client=mobile#comparison-good-cwvs">24% for mobile clients</a>. This means it is all too likely that a 1.5-second TTFB is the norm for WordPress sites. This is in part what contributes to WordPress lagging behind most other CMSes for the LCP metric <a href="https://lookerstudio.google.com/s/k9MqdchHqw4">on mobile</a> and <a href="https://lookerstudio.google.com/s/j4AmmUaraQM">on desktop</a>, even about 10% below the average on all measured sites.</p>



<p>In comparison with the other CWV metrics—<a href="https://web.dev/articles/cls">Cumulative Layout Shift</a> (CLS) and <a href="https://web.dev/articles/inp">Interaction to Next Paint</a> (INP)—WordPress is doing worse in terms of LCP, as evident in the metric passing rates from the following <a href="https://httparchive.org/reports/techreport/landing">reports</a> on HTTP Archive:</p>



<figure class="wp-block-table is-style-regular"><table><thead><tr><th class="has-text-align-right" data-align="right">Device</th><th class="has-text-align-center" data-align="center">LCP</th><th class="has-text-align-center" data-align="center">CLS</th><th class="has-text-align-center" data-align="center">INP</th></tr></thead><tbody><tr><td class="has-text-align-right" data-align="right"><a href="https://httparchive.org/reports/techreport/tech?client=mobile&amp;tech=WordPress&amp;geo=ALL&amp;rank=ALL&amp;page=1#good-cwvs">Mobile</a></td><td class="has-text-align-center" data-align="center"><mark style="background-color:#FFEE58" class="has-inline-color">53%</mark></td><td class="has-text-align-center" data-align="center">83%</td><td class="has-text-align-center" data-align="center">86%</td></tr><tr><td class="has-text-align-right" data-align="right"><a href="https://httparchive.org/reports/techreport/tech?client=desktop&amp;tech=WordPress&amp;geo=ALL&amp;rank=ALL&amp;page=1#good-cwvs">Desktop</a></td><td class="has-text-align-center" data-align="center"><mark style="background-color:#FFEE58" class="has-inline-color">64%</mark></td><td class="has-text-align-center" data-align="center">71%</td><td class="has-text-align-center" data-align="center">98%</td></tr></tbody></table></figure>



<p>Therefore, improving LCP remains the most important focus for performance optimizations in WordPress. So in this post I&#8217;ll focus on the LCP impact for the plugins featured in Performance Lab and some other changes proposed for WordPress 6.9.</p>



<h3 class="wp-block-heading" id="benchmarking">Benchmarking</h3>



<p>Because the Lighthouse score is variable and a 100 score merely reflects a “good” LCP, evaluating the performance benefit of an optimization requires measuring the LCP metric itself. Due to the variability in the metric, it&#8217;s important to obtain the median value of the LCP over many measurements. By capturing the median LCP value before and after an optimization is applied, the relative impact on performance can be measured.</p>



<p>The tool I use for benchmarking LCP is in the <a href="https://github.com/GoogleChromeLabs/wpp-research">GoogleChromeLabs/​wpp-research</a> repo, which my team developed when I was at Google. Specifically, I use the <code><a href="https://github.com/GoogleChromeLabs/wpp-research/tree/main/cli#benchmark-web-vitals">benchmark-web-vitals</a></code> command which includes the ability to emulate mobile and desktop devices, network connections, and CPU speeds.</p>



<p>Here&#8217;s an example command I use to benchmark two URLs emulating a mobile device on a Fast 4G connection, and compare their results:</p>


<pre class="wp-block-code"><span><code class="hljs language-javascript">npm run research -- benchmark-web-vitals \
	--url=<span class="hljs-string">"http://localhost/?enable_plugins=none"</span> \
	--url=<span class="hljs-string">"http://localhost/?enable_plugins=foo"</span> \
	--number=<span class="hljs-number">50</span> \
	--network-conditions=<span class="hljs-string">"Fast 4G"</span> \
	--emulate-device=<span class="hljs-string">"Moto G4"</span> \
	--diff \
	--output=md</code></span></pre>


<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>Helper mu-plugin to override active plugins via query vars</summary>
<p>I threw this together to help me with benchmarking so that I didn&#8217;t have to manually activate/deactivate plugins constantly.</p>


<pre class="wp-block-code"><span><code class="hljs language-php"><span class="hljs-meta">&lt;?php</span>
<span class="hljs-comment">/**
 * Plugin Name: Active Plugins Override
 */</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">ActivePluginsOverride</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">get_always_active_plugins</span><span class="hljs-params">()</span>: <span class="hljs-title">array</span> </span>{
	<span class="hljs-keyword">return</span> <span class="hljs-keyword">array</span>(
		<span class="hljs-string">'user-switching/user-switching.php'</span>
	);
}

add_filter(
	<span class="hljs-string">'option_active_plugins'</span>,
	<span class="hljs-keyword">static</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">( $plugins )</span> </span>{
		<span class="hljs-keyword">return</span> array_unique( array_merge( $plugins, get_always_active_plugins() ) );
	},
	<span class="hljs-number">100</span>
);

<span class="hljs-keyword">if</span> ( <span class="hljs-keyword">isset</span>( $_GET&#91;<span class="hljs-string">'disable_all_plugins'</span>] ) || ( <span class="hljs-keyword">isset</span>( $_GET&#91;<span class="hljs-string">'enable_plugins'</span>] ) &amp;&amp; $_GET&#91;<span class="hljs-string">'enable_plugins'</span>] === <span class="hljs-string">'none'</span> ) ) {
	add_filter( <span class="hljs-string">'option_active_plugins'</span>, <span class="hljs-string">'__return_empty_array'</span> );
}

<span class="hljs-keyword">if</span> ( <span class="hljs-keyword">isset</span>( $_GET&#91;<span class="hljs-string">'disable_plugins'</span>] ) ) {
	<span class="hljs-keyword">if</span> ( is_array( $_GET&#91;<span class="hljs-string">'disable_plugins'</span>] ) ) {
		$disable_plugins = $_GET&#91;<span class="hljs-string">'disable_plugins'</span>];
	} <span class="hljs-keyword">else</span> {
		$disable_plugins = explode( <span class="hljs-string">','</span>, $_GET&#91;<span class="hljs-string">'disable_plugins'</span>] );
	}

	add_filter(
		<span class="hljs-string">'option_active_plugins'</span>,
		<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">( $active_plugins )</span> <span class="hljs-title">use</span> <span class="hljs-params">( $disable_plugins )</span> </span>{
			<span class="hljs-keyword">return</span> array_merge(
				array_filter(
					$active_plugins,
					<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">( $active_plugin )</span> <span class="hljs-title">use</span> <span class="hljs-params">( $disable_plugins )</span> </span>{
						$slug = strtok( $active_plugin, <span class="hljs-string">'/'</span> );
						<span class="hljs-keyword">return</span> ! in_array( $slug, $disable_plugins );
					}
				),
				get_always_active_plugins()
			);
		}
	);
}

<span class="hljs-keyword">if</span> ( <span class="hljs-keyword">isset</span>( $_GET&#91;<span class="hljs-string">'enable_plugins'</span>] ) ) {
	<span class="hljs-keyword">if</span> ( is_array( $_GET&#91;<span class="hljs-string">'enable_plugins'</span>] ) ) {
		$enable_plugins = $_GET&#91;<span class="hljs-string">'enable_plugins'</span>];
	} <span class="hljs-keyword">else</span> {
		$enable_plugins = explode( <span class="hljs-string">','</span>, $_GET&#91;<span class="hljs-string">'enable_plugins'</span>] );
	}

	<span class="hljs-keyword">if</span> ( count( array_intersect( $enable_plugins, <span class="hljs-keyword">array</span>( <span class="hljs-string">'embed-optimizer'</span>, <span class="hljs-string">'image-prioritizer'</span> ) ) ) &gt; <span class="hljs-number">0</span> ) {
		$enable_plugins&#91;] = <span class="hljs-string">'optimization-detective'</span>;
		$enable_plugins&#91;] = <span class="hljs-string">'od-admin-ui'</span>;
	}

	add_filter(
		<span class="hljs-string">'option_active_plugins'</span>,
		<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">( $active_plugins )</span> <span class="hljs-title">use</span> <span class="hljs-params">( $enable_plugins )</span> </span>{
			<span class="hljs-keyword">return</span> array_filter(
				$active_plugins,
				<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">( $active_plugin )</span> <span class="hljs-title">use</span> <span class="hljs-params">( $enable_plugins )</span> </span>{
					$slug = strtok( $active_plugin, <span class="hljs-string">'/'</span> );
					<span class="hljs-keyword">return</span> in_array( $slug, $enable_plugins );
				}
			);
		}
	);
}</code></span></pre></details>



<p>This results in a table like the following, showing the median metrics for the number of requests to both URLs:</p>



<figure class="wp-block-table benchmark-web-vitals"><table><thead><tr><th>Metric</th><th>Before</th><th>After</th><th>Diff (ms)</th><th>Diff (%)</th></tr></thead><tbody><tr><td>FCP</td><td>436.1</td><td>438.6</td><td>+2.5</td><td>+0.6%</td></tr><tr><td>LCP</td><td>915.3</td><td>690.9</td><td>-224.4</td><td><mark style="background-color:#FFEE58" class="has-inline-color">-24.5%</mark></td></tr><tr><td>TTFB</td><td>50.8</td><td>50.6</td><td>-0.2</td><td>-0.3%</td></tr><tr><td>LCP-TTFB</td><td>865.6</td><td>638.3</td><td>-227.3</td><td>-26.3%</td></tr></tbody></table></figure>



<p>In this example, the LCP improved by ~25% by enabling the “foo” plugin, which is exactly the kind of performance improvement we&#8217;re looking for on the Core Performance Team. Note this “LCP-TTFB” metric is simply the LCP metric minus the TTFB metric; this allows for measuring the client-side contributions to LCP by discounting any server-side variability in generating the response. The LCP-TTFB metric is important considering the lack of page caching on a local environment, and that certain optimizations may increase TTFB when page caching is not involved. For WordPress to scale, it&#8217;s important to have some <a href="https://developer.wordpress.org/advanced-administration/performance/cache/">page caching layer</a> in place.</p>



<h2 class="wp-block-heading" id="analyzing-optimization-impact-on-lcp">Analyzing Optimization Impact on LCP</h2>



<p>I&#8217;m going to analyze the impact of the following feature plugins featured in Performance Lab:</p>



<ul class="wp-block-list">
<li><a href="#image-placeholders">Image Placeholders</a></li>



<li><a href="#modern-image-formats">Modern Image Formats</a></li>



<li><a href="#enhanced-responsive-images">Enhanced Responsive Images</a></li>



<li><a href="#image-prioritizer">Image Prioritizer</a></li>



<li><a href="#speculative-loading">Speculative Loading</a></li>



<li><a href="#view-transitions">View Transitions</a></li>
</ul>



<p>I&#8217;m not covering <a href="https://wordpress.org/plugins/performant-translations/">Performant Translations</a> since it was mostly <a href="https://make.wordpress.org/core/2023/11/08/merging-performant-translations-into-core/">merged</a> into core as of 6.5. I&#8217;m also not covering <a href="https://wordpress.org/plugins/embed-optimizer/">Embed Optimizer</a> since it primarily helps with <abbr title="Interaction to Next Paint">INP</abbr> by lazy-loading and <abbr title="Cumulative Layout Shfit">CLS</abbr> by reserving space for resizing embeds; the LCP improvement is difficult to measure for embeds that appear in the initial viewport given their cross-origin nature. Lastly, I&#8217;m not covering <a href="https://wordpress.org/plugins/web-worker-offloading/">Web Worker Offloading</a> since it is quite experimental and it is only related to <abbr title="Interaction to Next Paint">INP</abbr>. However, I am going to cover enhancements beyond Performance Lab being targeted for WordPress 6.9:</p>



<ul class="wp-block-list">
<li><a href="#nocache-bfcache">Instant Back/Forward</a> (actually, brand new to Performance Lab)</li>



<li><a href="#script-module-deprioritization">Script Module Deprioritization</a></li>



<li><a href="#minified-css-inlining">Minified CSS Inlining</a></li>
</ul>



<p>The first four Performance Lab feature plugins are all related to images. In focusing on improving the LCP metric, this makes sense because images are the LCP element 73.3% of the time on mobile and 83.3% of the time on desktop, <a href="https://almanac.httparchive.org/en/2024/performance#lcp-content-types">according</a> to Web Almanac 2024:</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bcdfb93f2&quot;}" data-wp-interactive="core/image" data-wp-key="6975bcdfb93f2" class="wp-block-image aligncenter size-large wp-lightbox-container"><img data-recalc-dims="1" data-dominant-color="edf0f1" data-has-transparency="false" style="--dominant-color: #edf0f1;" loading="lazy" decoding="async" width="700" height="433" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 645px) 100vw, 645px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/largest-contentful-paint-top-content-types.png?resize=700%2C433&#038;ssl=1" alt="Bar chart showing the top LCP content types for desktop and mobile in 2024. For desktop, 83.3% of pages have images as the LCP content type, while 73.3% of mobile pages have images as their LCP content. Text accounts for 16.3% of LCP content on desktop and 26.3% on mobile. Inline images are rare, making up 0.3% of LCP content on desktop and 0.4% on mobile." class="wp-image-36050 not-transparent" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/largest-contentful-paint-top-content-types.png?resize=700%2C433&amp;ssl=1 700w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/largest-contentful-paint-top-content-types.png?resize=300%2C186&amp;ssl=1 300w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/largest-contentful-paint-top-content-types.png?resize=768%2C475&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/largest-contentful-paint-top-content-types.png?resize=150%2C93&amp;ssl=1 150w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/largest-contentful-paint-top-content-types.png?w=1200&amp;ssl=1 1200w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button><figcaption class="wp-element-caption">Top three LCP content types segmented by device.</figcaption></figure>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading" id="image-placeholders">Image Placeholders</h3>



<figure class="wp-block-embed is-type-wp-embed is-provider-plugin-directory wp-block-embed-plugin-directory"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="d5Rh7zWqgf"><a href="https://wordpress.org/plugins/dominant-color-images/">Image Placeholders</a></blockquote><iframe loading="lazy" class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  title="&#8220;Image Placeholders&#8221; &#8212; Plugin Directory" src="https://wordpress.org/plugins/dominant-color-images/embed/#?secret=k3UsKC63Cc#?secret=d5Rh7zWqgf" data-secret="d5Rh7zWqgf" width="500" height="282" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>



<p>The <a href="https://wordpress.org/plugins/dominant-color-images/">Image Placeholders</a> plugin, originally called “Dominant Color Images”, adds a non-transparent image&#8217;s dominant color as the background color. This improves the perceived page loading experience by showing <em>something</em> sooner, rather than just a blank spot on the page.</p>



<p>Instead of this:</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bcdfba425&quot;}" data-wp-interactive="core/image" data-wp-key="6975bcdfba425" class="wp-block-image size-large wp-lightbox-container"><img data-recalc-dims="1" data-dominant-color="e6e5e0" data-has-transparency="false" style="--dominant-color: #e6e5e0;" loading="lazy" decoding="async" width="700" height="381" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 645px) 100vw, 645px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/dominan-color-images-before.png?resize=700%2C381&#038;ssl=1" alt="" class="wp-image-36047 not-transparent" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/dominan-color-images-before-scaled.png?resize=700%2C381&amp;ssl=1 700w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/dominan-color-images-before-scaled.png?resize=300%2C163&amp;ssl=1 300w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/dominan-color-images-before-scaled.png?resize=768%2C418&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/dominan-color-images-before-scaled.png?resize=1536%2C836&amp;ssl=1 1536w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/dominan-color-images-before-scaled.png?resize=2048%2C1115&amp;ssl=1 2048w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/dominan-color-images-before-scaled.png?resize=150%2C82&amp;ssl=1 150w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<p>With the plugin active (and the media regenerated), the following is the result:</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bcdfbafc6&quot;}" data-wp-interactive="core/image" data-wp-key="6975bcdfbafc6" class="wp-block-image size-large wp-lightbox-container"><img data-recalc-dims="1" data-dominant-color="d9d5cc" data-has-transparency="false" style="--dominant-color: #d9d5cc;" loading="lazy" decoding="async" width="700" height="381" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 645px) 100vw, 645px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/dominan-color-images-after.png?resize=700%2C381&#038;ssl=1" alt="" class="wp-image-36048 not-transparent" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/dominan-color-images-after-scaled.png?resize=700%2C381&amp;ssl=1 700w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/dominan-color-images-after-scaled.png?resize=300%2C163&amp;ssl=1 300w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/dominan-color-images-after-scaled.png?resize=768%2C418&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/dominan-color-images-after-scaled.png?resize=1536%2C836&amp;ssl=1 1536w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/dominan-color-images-after-scaled.png?resize=2048%2C1115&amp;ssl=1 2048w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/dominan-color-images-after-scaled.png?resize=150%2C82&amp;ssl=1 150w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<p>The visual impact that this plugin has on the loading of the page is that there is a brown rectangle serving as a placeholder for where the user can expect an image to load.</p>



<p>However, when benchmarking the web vitals, there is <em>no improvement</em> in LCP. In fact, there even appears to be a slight regression:</p>



<figure class="wp-block-table benchmark-web-vitals"><table><thead><tr><th></th><th>Before</th><th>After</th><th>Diff (ms)</th><th>Diff (%)</th></tr></thead><tbody><tr><td>FCP</td><td>437.2</td><td>439.5</td><td>+2.3</td><td>+0.5%</td></tr><tr><td>LCP</td><td>610.8</td><td>613.5</td><td>+2.7</td><td><strong><mark style="background-color:#FFEE58" class="has-inline-color">+0.4%</mark></strong></td></tr><tr><td>TTFB</td><td>44.2</td><td>44.0</td><td>-0.2</td><td>-0.5%</td></tr><tr><td>LCP-TTFB</td><td>566.3</td><td>568.1</td><td>+1.8</td><td>+0.3%</td></tr></tbody></table><figcaption class="wp-element-caption">Benchmark results for 250 requests each, before and after, emulating Moto G4 over Fast 4G connection.</figcaption></figure>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>Benchmark command</summary><pre class="wp-block-code"><span><code class="hljs language-javascript">npm run research -- benchmark-web-vitals \
	--url=<span class="hljs-string">"https://wcus-perf-talk-demo.local/2025/07/30/bison-featured-image/?enable_plugins=none"</span> \
	--url=<span class="hljs-string">"https://wcus-perf-talk-demo.local/2025/07/30/bison-featured-image/?enable_plugins=dominant-color-images"</span> \
	--number=<span class="hljs-number">250</span> \
	--network-conditions=<span class="hljs-string">"Fast 4G"</span> \
	--emulate-device=<span class="hljs-string">"Moto G4"</span> \
	--diff \
	--output=md</code></span></pre></details>



<p>Moreover, there is <strong>no difference</strong> in the Lighthouse performance score which is already maxed out at 100 (but again, this doesn&#8217;t mean perfection). Nevertheless, just because there is no improvement on the raw performance metric, this doesn&#8217;t mean there isn&#8217;t value in doing it. User-perceived performance is also important, as long as it doesn&#8217;t negatively impact LCP (which should hopefully not conflict). We&#8217;ll revisit this later with <a href="#view-transitions">View Transitions</a>.</p>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading" id="modern-image-formats">Modern Image Formats</h3>



<figure class="wp-block-embed is-type-wp-embed is-provider-plugin-directory wp-block-embed-plugin-directory"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="cAKvmErQYI"><a href="https://wordpress.org/plugins/webp-uploads/">Modern Image Formats</a></blockquote><iframe loading="lazy" class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  title="&#8220;Modern Image Formats&#8221; &#8212; Plugin Directory" src="https://wordpress.org/plugins/webp-uploads/embed/#?secret=wCneE9etEj#?secret=cAKvmErQYI" data-secret="cAKvmErQYI" width="500" height="282" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>



<p>Modern image formats, like WebP and AVIF, are able to compress much higher compared to older formats like JPEG and PNG. For example, an image compressed with AVIF could be 50% smaller than a JPEG with similar visual quality. It stands to reason that if an image is smaller, then it will take less time to download, and the LCP metric will be improved since the image can render sooner. This also addresses a common audit you encounter in Lighthouse to <a href="https://developer.chrome.com/docs/lighthouse/performance/uses-webp-images">serve images in next-gen formats</a>:</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bcdfbc35e&quot;}" data-wp-interactive="core/image" data-wp-key="6975bcdfbc35e" class="wp-block-image size-large has-custom-border wp-lightbox-container"><img data-recalc-dims="1" data-dominant-color="f2f2f2" data-has-transparency="false" loading="lazy" decoding="async" width="700" height="432" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 645px) 100vw, 645px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-audit-serve-images-in-next-gen-formats-cropped.png?resize=700%2C432&#038;ssl=1" alt="Serve images in next-gen formats — Est savings of 46 KiB

Image formats like WebP and AVIF often provide better compression than PNG or JPEG, which means faster downloads and less data consumption. Learn more about modern image formats. FCP LCP

Consider using the Performance Lab plugin to automatically convert your uploaded JPEG images into WebP, wherever supported." class="has-border-color has-contrast-border-color wp-image-36137 not-transparent" style="--dominant-color: #f2f2f2; border-width:1px;box-shadow:var(--wp--preset--shadow--natural)" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-audit-serve-images-in-next-gen-formats-cropped-scaled.png?resize=700%2C432&amp;ssl=1 700w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-audit-serve-images-in-next-gen-formats-cropped-scaled.png?resize=300%2C185&amp;ssl=1 300w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-audit-serve-images-in-next-gen-formats-cropped-scaled.png?resize=768%2C474&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-audit-serve-images-in-next-gen-formats-cropped-scaled.png?resize=1536%2C948&amp;ssl=1 1536w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-audit-serve-images-in-next-gen-formats-cropped-scaled.png?resize=2048%2C1264&amp;ssl=1 2048w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-audit-serve-images-in-next-gen-formats-cropped-scaled.png?resize=150%2C93&amp;ssl=1 150w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<p>Note that the audit here estimates that the image in a modern image format would be 54% smaller for this image. (Note also the shameless plug for Performance Lab thanks to the <a href="https://github.com/GoogleChrome/lighthouse-stack-packs">Stack Pack</a> for WordPress.)</p>



<p>The <a href="https://wordpress.org/plugins/webp-uploads/">Modern Image Formats</a> plugin (originally called “WebP Uploads”) addresses this audit&#8217;s complaint by converting uploaded images into AVIF or WebP, depending on which is available on your server. With the plugin active, the original <a href="https://en.wikipedia.org/wiki/File:American_bison_k5680-1.jpg">Bison <img src="https://weston.ruter.net/wp-content/plugins/local-twemoji/images/emoji/72x72/1f9ac.png?ver=17-0-2-2" alt="🦬" class="wp-smiley" style="height: 1em; max-height: 1em;" /> image</a> uploaded as a JPEG is compressed from 356 KB down to 292 KB in AVIF format. This is ~18% smaller, not the hoped-for ~50% reduction in file size. Nevertheless, will this yield a 18% improvement in LCP? Here are the results of testing the same page as when testing Image Placeholders above, a post where the featured image is the LCP element:</p>



<figure class="wp-block-table benchmark-web-vitals"><table><thead><tr><th>Metric</th><th>Before</th><th>After</th><th>Diff (ms)</th><th>Diff (%)</th></tr></thead><tbody><tr><td>FCP</td><td>438.8</td><td>426.8</td><td>-12.1</td><td>-2.7%</td></tr><tr><td>LCP</td><td>613.8</td><td>599.2</td><td>-14.6</td><td><strong><mark style="background-color:#FFEE58" class="has-inline-color">-2.4%</mark></strong></td></tr><tr><td>TTFB</td><td>47.8</td><td>49.2</td><td>+1.4</td><td>+2.8%</td></tr><tr><td>LCP-TTFB</td><td>565.1</td><td>550.6</td><td>-14.5</td><td>-2.6%</td></tr></tbody></table><figcaption class="wp-element-caption">Benchmark results for 50 requests each, before and after, emulating Moto G4 over Fast 4G connection.</figcaption></figure>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>Benchmark command</summary><pre class="wp-block-code"><span><code class="hljs language-javascript">npm run research -- benchmark-web-vitals \
	--url=<span class="hljs-string">"https://wcus-perf-talk-demo.local/2025/07/30/bison-featured-image/?enable_plugins=none"</span> \
	--url=<span class="hljs-string">"https://wcus-perf-talk-demo.local/2025/07/30/bison-featured-image/?enable_plugins=webp-uploads"</span> \
	--number=<span class="hljs-number">50</span> \
	--network-conditions=<span class="hljs-string">"Fast 4G"</span> \
	--emulate-device=<span class="hljs-string">"Moto G4"</span> \
	--diff \
	--output=md</code></span></pre></details>



<p>So while the image file size was reduced ~20%, the LCP improvement here was only ~2%. </p>



<p>Brendan Kenny&#8217;s article on <a href="https://web.dev/blog/common-misconceptions-lcp#lcp_sub-part_breakdown">Common Misconceptions About How to Optimize LCP</a> shows that among the LCP sub-parts, the TTFB and “image load delay” contribute much more to the overall time compared with actually downloading the image resource. (Also described in <a href="https://almanac.httparchive.org/en/2024/performance#lcp-sub-parts">Web Almanac</a>.) <a href="https://remkusdevries.com/">Remkus de Vries</a> has likewise emphasized that we should <a href="https://remkusdevries.com/stop-obsessing-over-image-optimization/">Stop Obsessing Over Image Optimization</a>. We absolutely shouldn&#8217;t be serving 10 MB images to visitors, but there are diminishing returns for optimizing LCP with each percentage reduction in an image&#8217;s file size. There are far more impactful ways to improve LCP than to use the most optimal image compression.</p>



<p><em>Thanks to <a href="https://profiles.wordpress.org/adamsilverstein/">Adam Silverstein</a> for championing support for modern image formats both in this plugin and in core!</em></p>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading" id="enhanced-responsive-images">Enhanced Responsive Images</h3>



<figure class="wp-block-embed is-type-wp-embed is-provider-plugin-directory wp-block-embed-plugin-directory"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="Xl3qoM1V4i"><a href="https://wordpress.org/plugins/auto-sizes/">Enhanced Responsive Images</a></blockquote><iframe loading="lazy" class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  title="&#8220;Enhanced Responsive Images&#8221; &#8212; Plugin Directory" src="https://wordpress.org/plugins/auto-sizes/embed/#?secret=ddUBxcDy10#?secret=Xl3qoM1V4i" data-secret="Xl3qoM1V4i" width="500" height="282" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>



<p>The <a href="https://wordpress.org/plugins/auto-sizes/">Enhanced Responsive Images</a> plugin was originally developed as a way to automatically add <code>sizes=auto</code> for images with <code>loading=lazy</code>. This <a href="https://github.com/whatwg/html/pull/8008">new part</a> of the HTML spec lets the browser compute the responsive sizes because lazy-loaded images are loaded after the page has been laid out. This enhancement <a href="https://make.wordpress.org/core/2024/10/18/auto-sizes-for-lazy-loaded-images-in-wordpress-6-7/">landed</a> in WordPress 6.7. Since then, the scope of the plugin has changed to improve the calculation of the responsive <code>sizes</code> attribute for images which are not lazy-loaded.</p>



<p>By default, WordPress uses the same formula for constructing the default <code>sizes</code> attribute for all images. For example, if an image is 1024 pixels wide, then the <code>sizes</code> attribute is set to:</p>


<pre class="wp-block-code"><span><code class="hljs language-plaintext">(max-width: 1024px) 100vw, 1024px</code></span></pre>


<p>This is problematic, however, because if the image takes up half the width of the screen, then the browser will select the image URL from the <code>srcset</code> attribute for the size corresponding to the width of the viewport, not the width of the actual <code>IMG</code> element. This is often fine on mobile when images are more often taking up the full page width, but on desktop viewports it means a much larger image will be downloaded than is appropriate for the container size. For example, consider these images in a Columns block (sourced from Wikipedia, as linked):</p>



<div class="wp-block-columns is-not-stacked-on-mobile is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:66.66%">
<figure class="wp-block-image size-large"><a href="https://en.wikipedia.org/wiki/File:American_bison_k5680-1.jpg"><img data-recalc-dims="1" data-dominant-color="897956" data-has-transparency="false" style="--dominant-color: #897956;" loading="lazy" decoding="async" width="700" height="457" sizes="auto, (max-width: 429px) 100vw, 429px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/American_bison_k5680-1.jpg?resize=700%2C457&#038;ssl=1" alt="A Bison standing among grasses looking toward the camera." class="wp-image-34617 not-transparent" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/American_bison_k5680-1-scaled.jpg?resize=700%2C457&amp;ssl=1 700w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/American_bison_k5680-1-scaled.jpg?resize=300%2C196&amp;ssl=1 300w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/American_bison_k5680-1-scaled.jpg?resize=768%2C501&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/American_bison_k5680-1-scaled.jpg?resize=1536%2C1002&amp;ssl=1 1536w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/American_bison_k5680-1-scaled.jpg?resize=2048%2C1336&amp;ssl=1 2048w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/American_bison_k5680-1-scaled.jpg?resize=150%2C98&amp;ssl=1 150w" /></a></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.33%">
<figure class="wp-block-image size-large"><a href="https://commons.wikimedia.org/wiki/File:Bison_bison_Wichita_Mountain_Oklahoma.jpg"><img data-recalc-dims="1" data-dominant-color="887b5d" data-has-transparency="false" style="--dominant-color: #887b5d;" loading="lazy" decoding="async" width="700" height="507" sizes="auto, (max-width: 214px) 100vw, 214px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/Bison_bison_Wichita_Mountain_Oklahoma.jpg?resize=700%2C507&#038;ssl=1" alt="Bison bison at the Wichita Mountain Wildlife Refuge in Oklahoma." class="wp-image-36065 not-transparent" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/Bison_bison_Wichita_Mountain_Oklahoma-scaled.jpg?resize=700%2C507&amp;ssl=1 700w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/Bison_bison_Wichita_Mountain_Oklahoma-scaled.jpg?resize=300%2C217&amp;ssl=1 300w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/Bison_bison_Wichita_Mountain_Oklahoma-scaled.jpg?resize=768%2C556&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/Bison_bison_Wichita_Mountain_Oklahoma-scaled.jpg?resize=1536%2C1112&amp;ssl=1 1536w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/Bison_bison_Wichita_Mountain_Oklahoma-scaled.jpg?resize=2048%2C1483&amp;ssl=1 2048w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/Bison_bison_Wichita_Mountain_Oklahoma-scaled.jpg?resize=150%2C109&amp;ssl=1 150w" /></a></figure>



<figure class="wp-block-image size-large"><a href="https://commons.wikimedia.org/wiki/File:The_last_of_the_Canadian_buffaloes_Photo_No_580_(HS85-10-13487).jpg"><img data-recalc-dims="1" data-dominant-color="988b78" data-has-transparency="false" style="--dominant-color: #988b78;" loading="lazy" decoding="async" width="700" height="483" sizes="auto, (max-width: 214px) 100vw, 214px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/The_last_of_the_Canadian_buffaloes_Photo_No_580_HS85-10-13487.jpg?resize=700%2C483&#038;ssl=1" alt="The last of the Canadian buffaloes." class="wp-image-36066 not-transparent" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/The_last_of_the_Canadian_buffaloes_Photo_No_580_HS85-10-13487-scaled.jpg?resize=700%2C483&amp;ssl=1 700w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/The_last_of_the_Canadian_buffaloes_Photo_No_580_HS85-10-13487-scaled.jpg?resize=300%2C207&amp;ssl=1 300w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/The_last_of_the_Canadian_buffaloes_Photo_No_580_HS85-10-13487-scaled.jpg?resize=768%2C530&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/The_last_of_the_Canadian_buffaloes_Photo_No_580_HS85-10-13487-scaled.jpg?resize=1536%2C1060&amp;ssl=1 1536w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/The_last_of_the_Canadian_buffaloes_Photo_No_580_HS85-10-13487-scaled.jpg?resize=2048%2C1413&amp;ssl=1 2048w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/The_last_of_the_Canadian_buffaloes_Photo_No_580_HS85-10-13487-scaled.jpg?resize=150%2C104&amp;ssl=1 150w" /></a></figure>
</div>
</div>



<p>These images were all resized to be 1024 pixels wide, and so using the default WordPress scheme, they all have the same <code>sizes</code> attribute (as shown above), in spite of the fact that the first <code>IMG</code> element is twice the width of the second and third, and 1024px itself is about double the entire 645px width of the root Columns block on desktop. </p>



<p>In Lighthouse, the <a href="https://developer.chrome.com/docs/lighthouse/performance/uses-responsive-images">properly size images</a> audit correctly identifies these images as having inaccurate <code>sizes</code>:</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bcdfbef7a&quot;}" data-wp-interactive="core/image" data-wp-key="6975bcdfbef7a" class="wp-block-image size-large has-custom-border wp-lightbox-container"><img data-recalc-dims="1" data-dominant-color="f0efee" data-has-transparency="false" loading="lazy" decoding="async" width="625" height="700" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 625px) 100vw, 625px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-audit-properly-size-images-full-cropped.png?resize=625%2C700&#038;ssl=1" alt="Properly size images — Est savings of 226 KiB

Serve images that are appropriately-sized to save cellular data and improve load time. Learn how to size images.

Upload images directly through the media library to ensure that the required image sizes are available, and then insert them from the media library or use the image widget to ensure the optimal image sizes are used (including those for the responsive breakpoints). Avoid using Full Size images unless the dimensions are adequate for their usage.
" class="has-border-color has-accent-6-border-color wp-image-36138 not-transparent" style="--dominant-color: #f0efee; border-width:1px;box-shadow:var(--wp--preset--shadow--natural)" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-audit-properly-size-images-full-cropped.png?resize=625%2C700&amp;ssl=1 625w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-audit-properly-size-images-full-cropped.png?resize=268%2C300&amp;ssl=1 268w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-audit-properly-size-images-full-cropped.png?resize=768%2C861&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-audit-properly-size-images-full-cropped.png?resize=1371%2C1536&amp;ssl=1 1371w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-audit-properly-size-images-full-cropped.png?resize=1827%2C2048&amp;ssl=1 1827w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-audit-properly-size-images-full-cropped.png?resize=150%2C168&amp;ssl=1 150w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-audit-properly-size-images-full-cropped.png?w=2000&amp;ssl=1 2000w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<p>On my test page, this Columns block is at the beginning of the content, so none of the <code>IMG</code> tags are lazy-loaded and likewise none are eligible for auto-sizes. This is where the <em>enhanced</em> Enhanced Responsive Sizes plugin comes in. Now that auto-sizes was merged into core, the plugin&#8217;s scope has changed to improve the accuracy of the <code>sizes</code> attribute by using the structured layout information available in <a href="https://wordpress.org/documentation/article/block-themes/">block themes</a> (which is not available in classic themes). With this plugin active, the width in the <code>sizes</code> attribute for the <code>IMG</code> in the first column reduces from 1024px down to 429px:</p>


<pre class="wp-block-code"><span><code class="hljs language-plaintext">(max-width: 429px) 100vw, 429px</code></span></pre>


<p>And the two smaller <code>IMG</code> tags in the second narrower column get reduced from 1024px down to 134px:</p>


<pre class="wp-block-code"><span><code class="hljs language-plaintext">(max-width: 134px) 100vw, 134px</code></span></pre>


<p>Here is the performance impact when benchmarking the change:</p>



<figure class="wp-block-table benchmark-web-vitals"><table><thead><tr><th>Metric</th><th>Before</th><th>After</th><th>Diff (ms)</th><th>Diff (%)</th></tr></thead><tbody><tr><td>FCP</td><td>436.1</td><td>438.6</td><td>+2.5</td><td>+0.6%</td></tr><tr><td>LCP</td><td>915.3</td><td>690.9</td><td>-224.4</td><td><strong><mark style="background-color:#FFEE58" class="has-inline-color">-24.5%</mark></strong></td></tr><tr><td>TTFB</td><td>50.8</td><td>50.6</td><td>-0.2</td><td>-0.3%</td></tr><tr><td>LCP-TTFB</td><td>865.6</td><td>638.3</td><td>-227.3</td><td>-26.3%</td></tr></tbody></table><figcaption class="wp-element-caption">Benchmark results for 50 requests each, before and after, emulating Moto G4 over Fast 4G connection.</figcaption></figure>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>Benchmark command</summary><pre class="wp-block-code"><span><code class="hljs language-javascript">npm run research -- benchmark-web-vitals \
	--url=<span class="hljs-string">"https://wcus-perf-talk-demo.local/2025/07/31/bison-two-columns/?enable_plugins=none"</span> \
	--url=<span class="hljs-string">"https://wcus-perf-talk-demo.local/2025/07/31/bison-two-columns/?enable_plugins=auto-sizes"</span> \
	--number=<span class="hljs-number">50</span> \
	--network-conditions=<span class="hljs-string">"Fast 4G"</span> \
	--emulate-device=<span class="hljs-string">"Moto G4"</span> \
	--diff \
	--output=md</code></span></pre></details>



<p>This has a dramatic <strong>~25% reduction in LCP!</strong></p>



<p>This also has an improvement in the Lighthouse score for this example page, whereas I did not find an improvement when testing the previous plugins.</p>



<div class="wp-block-columns is-not-stacked-on-mobile is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bcdfc06e8&quot;}" data-wp-interactive="core/image" data-wp-key="6975bcdfc06e8" class="wp-block-image aligncenter size-large has-custom-border wp-lightbox-container"><img data-recalc-dims="1" data-dominant-color="f4f6f4" data-has-transparency="false" loading="lazy" decoding="async" width="700" height="473" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 322px) 100vw, 322px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-score-before-auto-sizes.png?resize=700%2C473&#038;ssl=1" alt="Lighthouse performance score 96 with an LCP of 2.7 seconds." class="has-border-color has-accent-6-border-color wp-image-36070 not-transparent" style="--dominant-color: #f4f6f4; box-shadow:var(--wp--preset--shadow--natural)" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-score-before-auto-sizes.png?resize=700%2C473&amp;ssl=1 700w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-score-before-auto-sizes.png?resize=300%2C203&amp;ssl=1 300w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-score-before-auto-sizes.png?resize=768%2C519&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-score-before-auto-sizes.png?resize=1536%2C1037&amp;ssl=1 1536w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-score-before-auto-sizes.png?resize=2048%2C1383&amp;ssl=1 2048w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-score-before-auto-sizes.png?resize=150%2C101&amp;ssl=1 150w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button><figcaption class="wp-element-caption">Before</figcaption></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bcdfc128b&quot;}" data-wp-interactive="core/image" data-wp-key="6975bcdfc128b" class="wp-block-image aligncenter size-large has-custom-border wp-lightbox-container"><img data-recalc-dims="1" data-dominant-color="f4f6f4" data-has-transparency="false" loading="lazy" decoding="async" width="700" height="473" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 322px) 100vw, 322px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-score-after-auto-sizes.png?resize=700%2C473&#038;ssl=1" alt="Lighthouse performance score 99 with an LCP of 2.0 seconds." class="has-border-color has-accent-6-border-color wp-image-36071 not-transparent" style="--dominant-color: #f4f6f4; border-width:1px;box-shadow:var(--wp--preset--shadow--natural)" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-score-after-auto-sizes.png?resize=700%2C473&amp;ssl=1 700w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-score-after-auto-sizes.png?resize=300%2C203&amp;ssl=1 300w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-score-after-auto-sizes.png?resize=768%2C519&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-score-after-auto-sizes.png?resize=1536%2C1037&amp;ssl=1 1536w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-score-after-auto-sizes.png?resize=2048%2C1383&amp;ssl=1 2048w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-score-after-auto-sizes.png?resize=150%2C101&amp;ssl=1 150w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button><figcaption class="wp-element-caption">After</figcaption></figure>
</div>
</div>



<p>The optimization also greatly improved the properly size images audit, before and after:</p>



<figure class="wp-block-image aligncenter size-large has-custom-border"><img data-recalc-dims="1" data-dominant-color="f7f0f0" data-has-transparency="false" loading="lazy" decoding="async" width="700" height="99" sizes="auto, (max-width: 645px) 100vw, 645px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-properly-size-images-audit-before-auto-sizes.png?resize=700%2C99&#038;ssl=1" alt="&#x26a0; Properly size images — Est savings of 225 KiB" class="has-border-color has-accent-6-border-color wp-image-36073 not-transparent" style="--dominant-color: #f7f0f0; border-width:1px" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-properly-size-images-audit-before-auto-sizes.png?resize=700%2C99&amp;ssl=1 700w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-properly-size-images-audit-before-auto-sizes.png?resize=300%2C43&amp;ssl=1 300w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-properly-size-images-audit-before-auto-sizes.png?resize=768%2C109&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-properly-size-images-audit-before-auto-sizes.png?resize=150%2C21&amp;ssl=1 150w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-properly-size-images-audit-before-auto-sizes.png?w=951&amp;ssl=1 951w" /></figure>



<figure class="wp-block-image aligncenter size-large has-custom-border"><img data-recalc-dims="1" data-dominant-color="f7f1f1" data-has-transparency="false" loading="lazy" decoding="async" width="700" height="99" sizes="auto, (max-width: 645px) 100vw, 645px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-properly-size-images-audit-after-auto-sizes.png?resize=700%2C99&#038;ssl=1" alt="&#x26a0; Properly size images — Est savings of 91 KiB" class="has-border-color has-accent-6-border-color wp-image-36074 not-transparent" style="--dominant-color: #f7f1f1; border-width:1px" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-properly-size-images-audit-after-auto-sizes.png?resize=700%2C99&amp;ssl=1 700w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-properly-size-images-audit-after-auto-sizes.png?resize=300%2C43&amp;ssl=1 300w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-properly-size-images-audit-after-auto-sizes.png?resize=768%2C109&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-properly-size-images-audit-after-auto-sizes.png?resize=150%2C21&amp;ssl=1 150w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-properly-size-images-audit-after-auto-sizes.png?w=951&amp;ssl=1 951w" /></figure>



<p>Note that this audit is unlikely to ever pass completely unless you generate many more intermediate image sizes to better fit all possible dimensions for your responsive images. This is something that an image CDN could do for you, however. </p>



<p>The effect of the more accurate <code>sizes</code> can be is evident in which intermediate image size files get downloaded:</p>



<figure class="wp-block-table"><table><thead><tr><th class="has-text-align-right" data-align="right"></th><th class="has-text-align-center" data-align="center">Before</th><th class="has-text-align-center" data-align="center">After</th><th class="has-text-align-right" data-align="right">Reduction</th></tr></thead><tbody><tr><td class="has-text-align-right" data-align="right"><img data-recalc-dims="1" data-dominant-color="897956" data-has-transparency="false" loading="lazy" decoding="async" width="50" height="33" class="wp-image-34617 not-transparent" style="--dominant-color: #897956; width: 50px;" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/American_bison_k5680-1-scaled.jpg?resize=50%2C33&#038;ssl=1" alt="A Bison standing among grasses looking toward the camera." srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/American_bison_k5680-1-scaled.jpg?w=2560&amp;ssl=1 2560w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/American_bison_k5680-1-scaled.jpg?resize=300%2C196&amp;ssl=1 300w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/American_bison_k5680-1-scaled.jpg?resize=700%2C457&amp;ssl=1 700w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/American_bison_k5680-1-scaled.jpg?resize=768%2C501&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/American_bison_k5680-1-scaled.jpg?resize=1536%2C1002&amp;ssl=1 1536w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/American_bison_k5680-1-scaled.jpg?resize=2048%2C1336&amp;ssl=1 2048w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/American_bison_k5680-1-scaled.jpg?resize=150%2C98&amp;ssl=1 150w" sizes="auto, (max-width: 50px) 100vw, 50px" /></td><td class="has-text-align-center" data-align="center">2048&#215;1336</td><td class="has-text-align-center" data-align="center">1024&#215;668</td><td class="has-text-align-right" data-align="right">-75%</td></tr><tr><td class="has-text-align-right" data-align="right"><img data-recalc-dims="1" data-dominant-color="887b5d" data-has-transparency="false" loading="lazy" decoding="async" width="50" height="36" class="wp-image-36065 not-transparent" style="--dominant-color: #887b5d; width: 50px;" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/Bison_bison_Wichita_Mountain_Oklahoma-scaled.jpg?resize=50%2C36&#038;ssl=1" alt="Bison bison at the Wichita Mountain Wildlife Refuge in Oklahoma." srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/Bison_bison_Wichita_Mountain_Oklahoma-scaled.jpg?w=2560&amp;ssl=1 2560w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/Bison_bison_Wichita_Mountain_Oklahoma-scaled.jpg?resize=300%2C217&amp;ssl=1 300w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/Bison_bison_Wichita_Mountain_Oklahoma-scaled.jpg?resize=700%2C507&amp;ssl=1 700w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/Bison_bison_Wichita_Mountain_Oklahoma-scaled.jpg?resize=768%2C556&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/Bison_bison_Wichita_Mountain_Oklahoma-scaled.jpg?resize=1536%2C1112&amp;ssl=1 1536w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/Bison_bison_Wichita_Mountain_Oklahoma-scaled.jpg?resize=2048%2C1483&amp;ssl=1 2048w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/Bison_bison_Wichita_Mountain_Oklahoma-scaled.jpg?resize=150%2C109&amp;ssl=1 150w" sizes="auto, (max-width: 50px) 100vw, 50px" /></td><td class="has-text-align-center" data-align="center">2048&#215;1483</td><td class="has-text-align-center" data-align="center">300&#215;217</td><td class="has-text-align-right" data-align="right">-98%</td></tr><tr><td class="has-text-align-right" data-align="right"><img data-recalc-dims="1" data-dominant-color="988b78" data-has-transparency="false" loading="lazy" decoding="async" width="50" height="35" class="wp-image-36066 not-transparent" style="--dominant-color: #988b78; width: 50px;" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/The_last_of_the_Canadian_buffaloes_Photo_No_580_HS85-10-13487-scaled.jpg?resize=50%2C35&#038;ssl=1" alt="The last of the Canadian buffaloes." srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/The_last_of_the_Canadian_buffaloes_Photo_No_580_HS85-10-13487-scaled.jpg?w=2560&amp;ssl=1 2560w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/The_last_of_the_Canadian_buffaloes_Photo_No_580_HS85-10-13487-scaled.jpg?resize=300%2C207&amp;ssl=1 300w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/The_last_of_the_Canadian_buffaloes_Photo_No_580_HS85-10-13487-scaled.jpg?resize=700%2C483&amp;ssl=1 700w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/The_last_of_the_Canadian_buffaloes_Photo_No_580_HS85-10-13487-scaled.jpg?resize=768%2C530&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/The_last_of_the_Canadian_buffaloes_Photo_No_580_HS85-10-13487-scaled.jpg?resize=1536%2C1060&amp;ssl=1 1536w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/The_last_of_the_Canadian_buffaloes_Photo_No_580_HS85-10-13487-scaled.jpg?resize=2048%2C1413&amp;ssl=1 2048w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/The_last_of_the_Canadian_buffaloes_Photo_No_580_HS85-10-13487-scaled.jpg?resize=150%2C104&amp;ssl=1 150w" sizes="auto, (max-width: 50px) 100vw, 50px" /></td><td class="has-text-align-center" data-align="center">2048&#215;1413</td><td class="has-text-align-center" data-align="center">300&#215;207</td><td class="has-text-align-right" data-align="right">-98%</td></tr></tbody><tfoot><tr><td class="has-text-align-right" data-align="right">Total:</td><td class="has-text-align-center" data-align="center">8,667,136px</td><td class="has-text-align-center" data-align="center">811,232px</td><td class="has-text-align-right" data-align="right">-91%</td></tr></tfoot></table></figure>



<p>In this test of images in a Columns block, what follows is the impact of Modern Image Formats with AVIF versus Enhanced Responsive Images with more accurate <code>sizes</code>, and then with them both active together:</p>



<figure class="wp-block-table"><table><thead><tr><th>Plugins</th><th class="has-text-align-right" data-align="right">Transferred</th><th class="has-text-align-right" data-align="right">Reduction</th></tr></thead><tbody><tr><td>None</td><td class="has-text-align-right" data-align="right">1,595 kB</td><td class="has-text-align-right" data-align="right">—</td></tr><tr><td>Modern Image Formats with AVIF</td><td class="has-text-align-right" data-align="right">1,137 kB</td><td class="has-text-align-right" data-align="right">29%</td></tr><tr><td>Enhanced Responsive Images</td><td class="has-text-align-right" data-align="right">206 kB</td><td class="has-text-align-right" data-align="right">87%</td></tr><tr><td>Both</td><td class="has-text-align-right" data-align="right">175 kB</td><td class="has-text-align-right" data-align="right">89%</td></tr></tbody></table></figure>



<p>As is evident, the use of a more accurate <code>sizes</code> attribute has three times the reduction in bytes compared with using the AVIF image format (87% vs 29%)! Adding AVIF on top of the better <code>sizes</code> only yields an additional 2% reduction in transferred bytes in this example. It&#8217;s no wonder why Enhanced Responsive Images has a greater impact on LCP compared with Modern Image Formats!</p>



<p><em>Props to <a href="https://profiles.wordpress.org/mukesh27/">Mukesh Panchal</a> and <a href="https://profiles.wordpress.org/joemcgill/">Joe McGill</a> for <a href="https://github.com/WordPress/performance/pulls?q=is%3Apr+label%3A%22%5BPlugin%5D+Enhanced+Responsive+Images%22+is%3Amerged">their work</a> on this! Joe also first <a href="https://joemcgill.net/2025/01/happy-10-yrs-for-default-sizes/">proposed</a> the original <code>sizes</code> attribute.</em></p>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading" id="image-prioritizer">Image Prioritizer</h3>



<figure class="wp-block-embed is-type-wp-embed is-provider-plugin-directory wp-block-embed-plugin-directory"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="Wf9JjEwWra"><a href="https://wordpress.org/plugins/image-prioritizer/">Image Prioritizer</a></blockquote><iframe loading="lazy" class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  title="&#8220;Image Prioritizer&#8221; &#8212; Plugin Directory" src="https://wordpress.org/plugins/image-prioritizer/embed/#?secret=5tMBroYGWR#?secret=Wf9JjEwWra" data-secret="Wf9JjEwWra" width="500" height="282" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>



<p>The last Performance Lab feature plugin which focuses on images is <a href="https://wordpress.org/plugins/image-prioritizer/">Image Prioritizer</a>. As indicated by the name, the plugin optimizes image loading prioritization. For example, it boosts the priority of the detected LCP image with <code>fetchpriority=​high</code> while also deprioritizing the loading of images outside the viewport with lazy-loading. This plugin depends on the <a href="https://wordpress.org/plugins/optimization-detective/">Optimization Detective</a> plugin as its framework for the optimizations it applies. I gave a <a href="https://weston.ruter.net/2025/02/21/boosting-performance-with-optimization-detective/">talk</a> at WordCamp Asia 2025 all about this plugin:</p>



<figure class="wp-block-embed is-type-wp-embed is-provider-weston-ruter wp-block-embed-weston-ruter"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="qTojL8lyXK"><a href="https://weston.ruter.net/2025/02/21/boosting-performance-with-optimization-detective/">Boosting Performance with Optimization Detective</a></blockquote><iframe loading="lazy" class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  title="&#8220;Boosting Performance with Optimization Detective&#8221; &#8212; Weston Ruter" src="https://weston.ruter.net/2025/02/21/boosting-performance-with-optimization-detective/embed/#?secret=i0sLa3CG1P#?secret=qTojL8lyXK" data-secret="qTojL8lyXK" width="500" height="282" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>



<p>In that talk, I cover Image Prioritizer in depth; the plugin description also has the <a href="https://wordpress.org/plugins/image-prioritizer/#:~:text=The%20current%20optimizations%20include%3A">full list of optimizations</a>. But I&#8217;ll highlight here a couple of the most impactful optimizations which improve the LCP metric for images.</p>



<h4 class="wp-block-heading" id="responsive-image-prioritization">Responsive Image Prioritization</h4>



<p>Take for example this gallery of three images (again, from Wikipedia as linked):</p>



<figure class="wp-block-gallery has-nested-images columns-default wp-block-gallery-1 is-layout-flex wp-block-gallery-is-layout-flex">
<figure class="wp-block-image size-large"><a href="https://commons.wikimedia.org/wiki/File:Bison_with_its_young.jpg"><img data-recalc-dims="1" data-dominant-color="7a6c32" data-has-transparency="false" style="--dominant-color: #7a6c32;" loading="lazy" decoding="async" width="700" height="467" sizes="auto, (max-width: 645px) 100vw, 645px" data-id="36117" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/Bison_with_its_young-2560w.jpg?resize=700%2C467&#038;ssl=1" alt="" class="wp-image-36117 not-transparent" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/Bison_with_its_young-2560w.jpg?resize=700%2C467&amp;ssl=1 700w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/Bison_with_its_young-2560w.jpg?resize=300%2C200&amp;ssl=1 300w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/Bison_with_its_young-2560w.jpg?resize=768%2C512&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/Bison_with_its_young-2560w.jpg?resize=1536%2C1025&amp;ssl=1 1536w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/Bison_with_its_young-2560w.jpg?resize=2048%2C1366&amp;ssl=1 2048w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/Bison_with_its_young-2560w.jpg?resize=150%2C100&amp;ssl=1 150w" /></a></figure>



<figure class="wp-block-image size-large"><a href="https://commons.wikimedia.org/wiki/File:Bison_bison_Wichita_Mountain_Oklahoma.jpg"><img data-recalc-dims="1" data-dominant-color="887b5d" data-has-transparency="false" style="--dominant-color: #887b5d;" loading="lazy" decoding="async" width="700" height="507" sizes="auto, (max-width: 645px) 100vw, 645px" data-id="36065" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/Bison_bison_Wichita_Mountain_Oklahoma.jpg?resize=700%2C507&#038;ssl=1" alt="Bison bison at the Wichita Mountain Wildlife Refuge in Oklahoma." class="wp-image-36065 not-transparent" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/Bison_bison_Wichita_Mountain_Oklahoma-scaled.jpg?resize=700%2C507&amp;ssl=1 700w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/Bison_bison_Wichita_Mountain_Oklahoma-scaled.jpg?resize=300%2C217&amp;ssl=1 300w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/Bison_bison_Wichita_Mountain_Oklahoma-scaled.jpg?resize=768%2C556&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/Bison_bison_Wichita_Mountain_Oklahoma-scaled.jpg?resize=1536%2C1112&amp;ssl=1 1536w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/Bison_bison_Wichita_Mountain_Oklahoma-scaled.jpg?resize=2048%2C1483&amp;ssl=1 2048w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/Bison_bison_Wichita_Mountain_Oklahoma-scaled.jpg?resize=150%2C109&amp;ssl=1 150w" /></a></figure>



<figure class="wp-block-image size-large has-custom-border"><a href="https://en.wikipedia.org/wiki/File:American_bison_k5680-1.jpg"><img data-recalc-dims="1" data-dominant-color="897956" data-has-transparency="false" loading="lazy" decoding="async" width="700" height="457" sizes="auto, (max-width: 645px) 100vw, 645px" data-id="34617" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/American_bison_k5680-1.jpg?resize=700%2C457&#038;ssl=1" alt="A Bison standing among grasses looking toward the camera." class="wp-image-34617 not-transparent" style="--dominant-color: #897956; border-style:none;border-width:0px" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/American_bison_k5680-1-scaled.jpg?resize=700%2C457&amp;ssl=1 700w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/American_bison_k5680-1-scaled.jpg?resize=300%2C196&amp;ssl=1 300w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/American_bison_k5680-1-scaled.jpg?resize=768%2C501&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/American_bison_k5680-1-scaled.jpg?resize=1536%2C1002&amp;ssl=1 1536w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/American_bison_k5680-1-scaled.jpg?resize=2048%2C1336&amp;ssl=1 2048w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/American_bison_k5680-1-scaled.jpg?resize=150%2C98&amp;ssl=1 150w" /></a></figure>
</figure>



<p>This gallery is configured without the “Crop images to fit” setting enabled. On desktop, the second image is the largest image of the three, and so it is the LCP element. However, on mobile it&#8217;s actually the third image which is the largest (and the LCP element) since it appears on a row by itself:</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:70%">
<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bcdfc4ad4&quot;}" data-wp-interactive="core/image" data-wp-key="6975bcdfc4ad4" class="wp-block-image aligncenter size-large has-custom-border wp-lightbox-container"><img data-recalc-dims="1" data-dominant-color="e3dcd2" data-has-transparency="false" loading="lazy" decoding="async" width="700" height="455" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 451px) 100vw, 451px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/gallery-block-with-lcp-element-on-desktop.png?resize=700%2C455&#038;ssl=1" alt="" class="has-border-color has-accent-6-border-color wp-image-36126 not-transparent" style="--dominant-color: #e3dcd2; border-width:1px;box-shadow:var(--wp--preset--shadow--natural)" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/gallery-block-with-lcp-element-on-desktop-scaled.png?resize=700%2C455&amp;ssl=1 700w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/gallery-block-with-lcp-element-on-desktop-scaled.png?resize=300%2C195&amp;ssl=1 300w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/gallery-block-with-lcp-element-on-desktop-scaled.png?resize=768%2C500&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/gallery-block-with-lcp-element-on-desktop-scaled.png?resize=1536%2C999&amp;ssl=1 1536w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/gallery-block-with-lcp-element-on-desktop-scaled.png?resize=2048%2C1332&amp;ssl=1 2048w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/gallery-block-with-lcp-element-on-desktop-scaled.png?resize=150%2C98&amp;ssl=1 150w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button><figcaption class="wp-element-caption">Desktop layout</figcaption></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:30%">
<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bcdfc59e4&quot;}" data-wp-interactive="core/image" data-wp-key="6975bcdfc59e4" class="wp-block-image aligncenter size-large has-custom-border wp-lightbox-container"><img data-recalc-dims="1" data-dominant-color="c9bdac" data-has-transparency="false" loading="lazy" decoding="async" width="394" height="700" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 193px) 100vw, 193px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/gallery-block-with-lcp-element-on-mobile.png?resize=394%2C700&#038;ssl=1" alt="" class="has-border-color has-accent-6-border-color wp-image-36127 not-transparent" style="--dominant-color: #c9bdac; border-width:1px;box-shadow:var(--wp--preset--shadow--natural)" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/gallery-block-with-lcp-element-on-mobile-scaled.png?resize=394%2C700&amp;ssl=1 394w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/gallery-block-with-lcp-element-on-mobile-scaled.png?resize=169%2C300&amp;ssl=1 169w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/gallery-block-with-lcp-element-on-mobile-scaled.png?resize=768%2C1366&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/gallery-block-with-lcp-element-on-mobile-scaled.png?resize=864%2C1536&amp;ssl=1 864w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/gallery-block-with-lcp-element-on-mobile-scaled.png?resize=1152%2C2048&amp;ssl=1 1152w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/gallery-block-with-lcp-element-on-mobile-scaled.png?resize=150%2C267&amp;ssl=1 150w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/gallery-block-with-lcp-element-on-mobile-scaled.png?w=1440&amp;ssl=1 1440w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button><figcaption class="wp-element-caption">Mobile layout</figcaption></figure>
</div>
</div>



<p>Nevertheless, WordPress core adds <code>fetchpriority=​high</code> to the <em>first</em> <code>IMG</code>, of the bison and calf, even though it is never the LCP element. WordPress adds the <code>fetchpriority</code> attribute to the first sufficiently-large image it finds on the page, making a best guess as to which is the LCP element. But even when core does add the attribute to the right image on a desktop viewport, it could be wrong for mobile, and vice versa. In <a href="https://github.com/GoogleChromeLabs/wpp-research/pull/73">my research</a>, when WordPress core correctly adds the <code>fetchpriority</code> attribute to the LCP <code>IMG</code> element on desktop or mobile, I found that 37% of those pages have a different <code>IMG</code> which is the LCP element for the other viewport. This means it&#8217;s only safe to use the <code>fetchpriority</code> attribute on <code>IMG</code> tags when they are the LCP element on <em>both</em> desktop and mobile (and tablet too). But WordPress doesn&#8217;t know how the page is laid out (although this is starting to change in the case of block themes, as with Enhanced Responsive Images above). This is where Optimization Detective comes in.</p>



<p>The Optimization Detective plugin provides a framework to capture measurements from site visitors about what elements are displayed on a page across a variety of device form factors and responsive breakpoints (e.g. desktop, tablet, and mobile). These measurements are stored in “URL Metrics” (a custom post type) which can then be used by extensions, like Image Prioritizer, to apply more accurate optimizations. In this case, Image Prioritizer:</p>



<ol class="wp-block-list">
<li>Removes <code>fetchpriority=​high</code> from the first <code>IMG</code> in the Gallery.</li>



<li>Adds responsive preload <code>LINK</code> tags for the actual LCP element based on media queries.</li>
</ol>



<p>For example, the following <code>LINK</code> tags are added to the page:</p>


<pre class="wp-block-code"><span><code class="hljs language-xml shcb-code-table"><span class='shcb-loc'><span><span class="hljs-tag">&lt;<span class="hljs-name">link</span></span>
</span></span><span class='shcb-loc'><span><span class="hljs-tag"> <span class="hljs-attr">rel</span>=<span class="hljs-string">"preload"</span> <span class="hljs-attr">as</span>=<span class="hljs-string">"image"</span> <span class="hljs-attr">fetchpriority</span>=<span class="hljs-string">"high"</span></span>
</span></span><mark class='shcb-loc'><span><span class="hljs-tag"> <span class="hljs-attr">href</span>=<span class="hljs-string">".../bison-2.jpg"</span></span>
</span></mark><span class='shcb-loc'><span><span class="hljs-tag"> <span class="hljs-attr">imagesrcset</span>=<span class="hljs-string">"..."</span> <span class="hljs-attr">imagesizes</span>=<span class="hljs-string">"..."</span></span>
</span></span><mark class='shcb-loc'><span><span class="hljs-tag"> <span class="hljs-attr">media</span>=<span class="hljs-string">"screen and (width &lt;= 480px)"</span></span>
</span></mark><span class='shcb-loc'><span><span class="hljs-tag">&gt;</span>
</span></span><span class='shcb-loc'><span><span class="hljs-tag">&lt;<span class="hljs-name">link</span></span>
</span></span><span class='shcb-loc'><span><span class="hljs-tag"> <span class="hljs-attr">rel</span>=<span class="hljs-string">"preload"</span> <span class="hljs-attr">as</span>=<span class="hljs-string">"image"</span> <span class="hljs-attr">fetchpriority</span>=<span class="hljs-string">"high"</span></span>
</span></span><mark class='shcb-loc'><span><span class="hljs-tag"> <span class="hljs-attr">href</span>=<span class="hljs-string">".../bison-3.jpg"</span></span>
</span></mark><span class='shcb-loc'><span><span class="hljs-tag"> <span class="hljs-attr">imagesrcset</span>=<span class="hljs-string">"..."</span> <span class="hljs-attr">imagesizes</span>=<span class="hljs-string">"..."</span></span>
</span></span><mark class='shcb-loc'><span><span class="hljs-tag"> <span class="hljs-attr">media</span>=<span class="hljs-string">"screen and (782px &lt; width)"</span></span>
</span></mark><span class='shcb-loc'><span><span class="hljs-tag">&gt;</span>
</span></span></code></span></pre>


<p>Note how the first <code>LINK</code> preloads the second bison image on mobile, but the second <code>LINK</code> preloads the third bison image on desktop. Here is the performance impact for these changes on mobile:</p>



<figure class="wp-block-table benchmark-web-vitals"><table><thead><tr><th>Metric</th><th>Before</th><th>After</th><th>Diff (ms)</th><th>Diff (%)</th></tr></thead><tbody><tr><td>FCP</td><td>441.9</td><td>449.5</td><td>+7.7</td><td>+1.7%</td></tr><tr><td>LCP</td><td>984.1</td><td>713.2</td><td>-270.9</td><td><strong><mark style="background-color:#FFEE58" class="has-inline-color">-27.5%</mark></strong></td></tr><tr><td>TTFB</td><td>49.4</td><td>53.5</td><td>+4.1</td><td>+8.3%</td></tr><tr><td>LCP-TTFB</td><td>935.1</td><td>659.5</td><td>-275.6</td><td>-29.5%</td></tr></tbody></table><figcaption class="wp-element-caption">Benchmark results for 50 requests each, before and after, emulating Moto G4 over Fast 4G connection.</figcaption></figure>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>Benchmark command</summary><pre class="wp-block-code"><span><code class="hljs language-javascript">npm run research -- benchmark-web-vitals \
	--url=<span class="hljs-string">"https://wcus-perf-talk-demo.local/2025/08/04/bison-gallery/?disable_all_plugins"</span> \
	--url=<span class="hljs-string">"https://wcus-perf-talk-demo.local/2025/08/04/bison-gallery/?enable_plugins=image-prioritizer"</span> \
	--number=<span class="hljs-number">50</span> \
	--network-conditions=<span class="hljs-string">"Fast 4G"</span> \
	--emulate-device=<span class="hljs-string">"Moto G4"</span> \
	--diff \
	--output=md</code></span></pre></details>



<p>This is the biggest LCP improvement I&#8217;ve yet shown, with a 27.5% reduction compared with the 24.5% improvement in Enhanced Responsive Images. This shows up as an improvement in the Lighthouse performance score, increasing from 95 to 99:</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bcdfc752a&quot;}" data-wp-interactive="core/image" data-wp-key="6975bcdfc752a" class="wp-block-image aligncenter size-large has-custom-border wp-lightbox-container"><img data-recalc-dims="1" data-dominant-color="f4f5f3" data-has-transparency="false" loading="lazy" decoding="async" width="700" height="462" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 322px) 100vw, 322px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-gallery-block-without-image-prioritizer-on-mobile.png?resize=700%2C462&#038;ssl=1" alt="Lighthouse performance score 95 with 2.9 second LCP." class="has-border-color has-accent-6-border-color wp-image-36128 not-transparent" style="--dominant-color: #f4f5f3; border-width:1px;box-shadow:var(--wp--preset--shadow--natural)" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-gallery-block-without-image-prioritizer-on-mobile.png?resize=700%2C462&amp;ssl=1 700w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-gallery-block-without-image-prioritizer-on-mobile.png?resize=300%2C198&amp;ssl=1 300w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-gallery-block-without-image-prioritizer-on-mobile.png?resize=768%2C506&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-gallery-block-without-image-prioritizer-on-mobile.png?resize=150%2C99&amp;ssl=1 150w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-gallery-block-without-image-prioritizer-on-mobile.png?w=1468&amp;ssl=1 1468w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button><figcaption class="wp-element-caption">Without Image Prioritizer</figcaption></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bcdfc813c&quot;}" data-wp-interactive="core/image" data-wp-key="6975bcdfc813c" class="wp-block-image aligncenter size-large has-custom-border wp-lightbox-container"><img data-recalc-dims="1" data-dominant-color="f3f5f3" data-has-transparency="false" loading="lazy" decoding="async" width="700" height="462" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 322px) 100vw, 322px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-gallery-block-with-image-prioritizer-on-mobile.png?resize=700%2C462&#038;ssl=1" alt="Lighthouse performance score 99 with 2.1 second LCP." class="has-border-color has-accent-6-border-color wp-image-36129 not-transparent" style="--dominant-color: #f3f5f3; border-width:1px;box-shadow:var(--wp--preset--shadow--natural)" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-gallery-block-with-image-prioritizer-on-mobile.png?resize=700%2C462&amp;ssl=1 700w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-gallery-block-with-image-prioritizer-on-mobile.png?resize=300%2C198&amp;ssl=1 300w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-gallery-block-with-image-prioritizer-on-mobile.png?resize=768%2C506&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-gallery-block-with-image-prioritizer-on-mobile.png?resize=150%2C99&amp;ssl=1 150w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-gallery-block-with-image-prioritizer-on-mobile.png?w=1468&amp;ssl=1 1468w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button><figcaption class="wp-element-caption">With Image Prioritizer</figcaption></figure>
</div>
</div>



<p>But what becomes truly impressive are the results on desktop:</p>



<figure class="wp-block-table benchmark-web-vitals"><table><thead><tr><th>Metric</th><th>Before</th><th>After</th><th>Diff (ms)</th><th>Diff (%)</th></tr></thead><tbody><tr><td>FCP</td><td>434.9</td><td>436.0</td><td>+1.2</td><td>+0.3%</td></tr><tr><td>LCP</td><td>1020.1</td><td>503.2</td><td>-517.0</td><td><mark style="background-color:#FFEE58" class="has-inline-color"><strong>-50.7%</strong></mark></td></tr><tr><td>TTFB</td><td>49.9</td><td>52.4</td><td>+2.6</td><td>+5.1%</td></tr><tr><td>LCP-TTFB</td><td>969.4</td><td>451.3</td><td>-518.2</td><td>-53.5%</td></tr></tbody></table></figure>



<p>The LCP improvement here on desktop is almost double the improvement on mobile, at an over 50% reduction in LCP! In other words, the LCP metric is cut in half! This shows impressively in the Lighthouse performance score increasing from 93 to 100:</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bcdfc8f36&quot;}" data-wp-interactive="core/image" data-wp-key="6975bcdfc8f36" class="wp-block-image aligncenter size-large has-custom-border wp-lightbox-container"><img data-recalc-dims="1" data-dominant-color="f6f8f7" data-has-transparency="false" loading="lazy" decoding="async" width="700" height="462" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 322px) 100vw, 322px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-gallery-block-without-image-prioritizer-on-desktop.png?resize=700%2C462&#038;ssl=1" alt="Lighthouse performance score 93 with 1.7 second LCP." class="has-border-color has-accent-6-border-color wp-image-36130 not-transparent" style="--dominant-color: #f6f8f7; border-width:1px;box-shadow:var(--wp--preset--shadow--natural)" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-gallery-block-without-image-prioritizer-on-desktop.png?resize=700%2C462&amp;ssl=1 700w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-gallery-block-without-image-prioritizer-on-desktop.png?resize=300%2C198&amp;ssl=1 300w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-gallery-block-without-image-prioritizer-on-desktop.png?resize=768%2C506&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-gallery-block-without-image-prioritizer-on-desktop.png?resize=150%2C99&amp;ssl=1 150w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-gallery-block-without-image-prioritizer-on-desktop.png?w=1468&amp;ssl=1 1468w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button><figcaption class="wp-element-caption">Without Image Prioritizer</figcaption></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bcdfc9a9d&quot;}" data-wp-interactive="core/image" data-wp-key="6975bcdfc9a9d" class="wp-block-image aligncenter size-large has-custom-border wp-lightbox-container"><img data-recalc-dims="1" data-dominant-color="f6f8f7" data-has-transparency="false" loading="lazy" decoding="async" width="700" height="462" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 322px) 100vw, 322px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-gallery-block-with-image-prioritizer-on-desktop.png?resize=700%2C462&#038;ssl=1" alt="Lighthouse performance score 100 with 0.6 second LCP." class="has-border-color has-accent-6-border-color wp-image-36131 not-transparent" style="--dominant-color: #f6f8f7; border-width:1px;box-shadow:var(--wp--preset--shadow--natural)" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-gallery-block-with-image-prioritizer-on-desktop.png?resize=700%2C462&amp;ssl=1 700w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-gallery-block-with-image-prioritizer-on-desktop.png?resize=300%2C198&amp;ssl=1 300w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-gallery-block-with-image-prioritizer-on-desktop.png?resize=768%2C506&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-gallery-block-with-image-prioritizer-on-desktop.png?resize=150%2C99&amp;ssl=1 150w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-gallery-block-with-image-prioritizer-on-desktop.png?w=1468&amp;ssl=1 1468w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button><figcaption class="wp-element-caption">With Image Prioritizer</figcaption></figure>
</div>
</div>



<h4 class="wp-block-heading" id="background-image-prioritization">Background Image Prioritization</h4>



<p>Related to there being different LCP <code>IMG</code> elements on desktop versus mobile is that the LCP element&#8217;s image may not be an <code>IMG</code> at all, but rather a <code>DIV</code> (or some other element) with a CSS <code>background-image</code>. This is a very common way that imagery is added in page builders. Background images are also present in WordPress core, such as in some classic themes&#8217; header images; background images are also on any WordPress site using the Cover block when using a fixed background or when adding a background image to a Group block. The prevalence of non-<code>IMG</code> LCP images is captured in this <a href="https://almanac.httparchive.org/en/2022/performance#lcp-content-types">data presented</a> in Web Almanac 2022, showing that the <code>DIV</code> (presumably with a background image) is the LCP element ~26% of the time compared with an <code>IMG</code> at 42% of the time:</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bcdfca830&quot;}" data-wp-interactive="core/image" data-wp-key="6975bcdfca830" class="wp-block-image aligncenter size-large wp-lightbox-container"><img data-recalc-dims="1" data-dominant-color="f7f8f8" data-has-transparency="false" style="--dominant-color: #f7f8f8;" loading="lazy" decoding="async" width="598" height="700" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 598px) 100vw, 598px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/top-lcp-element-types.png?resize=598%2C700&#038;ssl=1" alt="Bar chart showing IMG is the LCP element on 47% of desktop pages and 42% of mobile pages, DIV on 28% and 26% respectively, P on 6% and 9%, H1 on 3% and 5%, undetected on 3% and 3%, SECTION on 3% and 3%, H2 on 1% and 2%, A on 1% and 2%, SPAN on 1% and 1%, H3 on 0% and 1%, HEADER on 1% and 1%, LI on 1% and 1%, RS-SBG on 1% and 1%, TD on 1% and 1%, VIDEO on 0% and 0%, and finally H4 is the LCP element type on 0% of both desktop and mobile pages." class="wp-image-36141 not-transparent" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/top-lcp-element-types.png?resize=598%2C700&amp;ssl=1 598w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/top-lcp-element-types.png?resize=256%2C300&amp;ssl=1 256w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/top-lcp-element-types.png?resize=768%2C899&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/top-lcp-element-types.png?resize=150%2C176&amp;ssl=1 150w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/top-lcp-element-types.png?w=1200&amp;ssl=1 1200w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button><figcaption class="wp-element-caption">The percent of pages that have a given element as its LCP.</figcaption></figure>



<p>The problem with the <code>background-image</code> style is that it is CSS: there is no way for core to attach a <code>fetchpriority=​high</code> HTML attribute as can be done for LCP <code>IMG</code> candidates. Take the following page for example, where there is a parallax Cover block at the beginning of the content, followed by some paragraphs of text, and finally a Gallery block with five images in it. The black rectangle denotes the desktop viewport:</p>



<figure class="wp-block-image alignwide size-large"><img data-recalc-dims="1" data-dominant-color="dad8d4" data-has-transparency="false" style="--dominant-color: #dad8d4;" loading="lazy" decoding="async" width="700" height="349" sizes="auto, (max-width: 1340px) 100vw, 1340px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/parallax-cover-as-lcp-with-gallery-outside-viewport-cropped.png?resize=700%2C349&#038;ssl=1" alt="Depicting a desktop viewport with a Cover block at the top of the page, followed by paragraphs of text, and a Gallery block at the bottom of the page outside of desktop viewport." class="wp-image-36152 not-transparent" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/parallax-cover-as-lcp-with-gallery-outside-viewport-cropped-scaled.png?resize=700%2C349&amp;ssl=1 700w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/parallax-cover-as-lcp-with-gallery-outside-viewport-cropped-scaled.png?resize=300%2C149&amp;ssl=1 300w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/parallax-cover-as-lcp-with-gallery-outside-viewport-cropped-scaled.png?resize=768%2C383&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/parallax-cover-as-lcp-with-gallery-outside-viewport-cropped-scaled.png?resize=1536%2C765&amp;ssl=1 1536w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/parallax-cover-as-lcp-with-gallery-outside-viewport-cropped-scaled.png?resize=2048%2C1021&amp;ssl=1 2048w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/parallax-cover-as-lcp-with-gallery-outside-viewport-cropped-scaled.png?resize=150%2C75&amp;ssl=1 150w" /></figure>



<p class="has-text-align-center has-small-font-size">Cover image courtesy <a href="https://www.pexels.com/photo/brown-buffalo-on-green-grass-field-8148551/">Gintare</a> K. on Pexels. Other previously-unused images courtesy Wikipedia: <a href="https://commons.wikimedia.org/wiki/File:American_Bison_AdF.jpg">1</a>, <a href="https://commons.wikimedia.org/wiki/File:Waldbison_Bison_bison_athabascae_Tierpark_Hellabrunn-13.jpg">2</a>, <a href="https://commons.wikimedia.org/wiki/File:Wood_bison_in_the_snow_(52815312269).jpg">3</a>.</p>



<p>The <code>DIV</code> in the Cover block with the CSS <code>background-image</code> is the LCP element. Nevertheless, WordPress core is adding <code>fetchpriority=​high</code> to the first <code>IMG</code> in the Gallery block because it is the first sufficiently large image, just in terms of its <code>width</code> and <code>height</code> attributes. Additionally, WordPress core omits <code>loading=lazy</code> from the first three content images (the first three images in the Gallery), but they are not even visible on either the desktop or mobile viewports. The effect here is that the first three images of the Gallery are all loaded first <em>before</em> the all-important background image for the Cover block. Image Prioritizer fixes this by:</p>



<ol class="wp-block-list">
<li>Removing <code>fetchpriority=​high</code> from the first <code>IMG</code> in the Gallery, since it is not the LCP element.</li>



<li>Adding <code>loading=lazy</code> to the first three <code>IMG</code> tags in the Gallery, since none of them are visible in any initial viewport.</li>



<li>Adding a preload <code>LINK</code> for the CSS <code>background-image</code> so that it is properly prioritized.</li>
</ol>



<p>The preload <code>LINK</code> looks like the following:</p>


<pre class="wp-block-code"><span><code class="hljs language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">link</span>
 <span class="hljs-attr">rel</span>=<span class="hljs-string">"preload"</span>
 <span class="hljs-attr">as</span>=<span class="hljs-string">"image"</span>
 <span class="hljs-attr">fetchpriority</span>=<span class="hljs-string">"high"</span>
 <span class="hljs-attr">href</span>=<span class="hljs-string">".../bison.jpg"</span>
 <span class="hljs-attr">media</span>=<span class="hljs-string">"screen"</span>
&gt;</span></code></span></pre>


<p>Unlike with the responsive image prioritization, the Cover block here is the LCP element for both desktop and mobile, so here there is only one <code>LINK</code> and the <code>media</code> attribute doesn&#8217;t need to add any viewport constraints.</p>



<p>Here is the performance impact:</p>



<figure class="wp-block-table benchmark-web-vitals"><table><thead><tr><th>Metric</th><th>Before</th><th>After</th><th>Diff (ms)</th><th>Diff (%)</th></tr></thead><tbody><tr><td>FCP</td><td>436.5</td><td>433.9</td><td>-2.7</td><td>-0.6%</td></tr><tr><td>LCP</td><td>1042.4</td><td>579.8</td><td>-462.7</td><td><mark style="background-color:#FFEE58" class="has-inline-color"><strong>-44.4%</strong></mark></td></tr><tr><td>TTFB</td><td>49.0</td><td>53.1</td><td>+4.1</td><td>+8.4%</td></tr><tr><td>LCP-TTFB</td><td>994.6</td><td>526.9</td><td>-467.7</td><td>-47.0%</td></tr></tbody></table><figcaption class="wp-element-caption">Benchmark results for 50 requests each, before and after, emulating Moto G4 over Fast 4G connection.</figcaption></figure>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>Benchmark command</summary><pre class="wp-block-code"><span><code class="hljs language-javascript">npm run research -- benchmark-web-vitals \
	--url=https:<span class="hljs-comment">//wcus-perf-talk-demo.local/2025/08/04/cover-block/?disable_all_plugins \</span>
	--url=https:<span class="hljs-comment">//wcus-perf-talk-demo.local/2025/08/04/cover-block/?enable_plugins=image-prioritizer \</span>
	--number=<span class="hljs-number">50</span> \
	--network-conditions=<span class="hljs-string">"Fast 4G"</span> \
	--emulate-device=<span class="hljs-string">"Moto G4"</span> \
	--diff \
	--output=md</code></span></pre></details>



<p>This is the second-best improvement to LCP I&#8217;ve shown here in analyzing these plugins. The Lighthouse performance score is also improved from 92 to 99:</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bcdfcc5e2&quot;}" data-wp-interactive="core/image" data-wp-key="6975bcdfcc5e2" class="wp-block-image aligncenter size-large has-custom-border wp-lightbox-container"><img data-recalc-dims="1" data-dominant-color="f6f8f7" data-has-transparency="false" loading="lazy" decoding="async" width="700" height="468" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 322px) 100vw, 322px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-score-cover-block-without-image-prioritizer.png?resize=700%2C468&#038;ssl=1" alt="Lighthouse performance score of 92 with 3.3 second LCP" class="has-border-color has-accent-6-border-color wp-image-36154 not-transparent" style="--dominant-color: #f6f8f7; border-width:1px;box-shadow:var(--wp--preset--shadow--natural)" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-score-cover-block-without-image-prioritizer.png?resize=700%2C468&amp;ssl=1 700w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-score-cover-block-without-image-prioritizer.png?resize=300%2C201&amp;ssl=1 300w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-score-cover-block-without-image-prioritizer.png?resize=768%2C513&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-score-cover-block-without-image-prioritizer.png?resize=150%2C100&amp;ssl=1 150w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-score-cover-block-without-image-prioritizer.png?w=1454&amp;ssl=1 1454w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button><figcaption class="wp-element-caption">Before</figcaption></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bcdfcd4d4&quot;}" data-wp-interactive="core/image" data-wp-key="6975bcdfcd4d4" class="wp-block-image aligncenter size-large has-custom-border wp-lightbox-container"><img data-recalc-dims="1" data-dominant-color="f5f8f7" data-has-transparency="false" loading="lazy" decoding="async" width="700" height="468" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 322px) 100vw, 322px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-score-cover-block-with-image-prioritizer.png?resize=700%2C468&#038;ssl=1" alt="Lighthouse performance score of 92 with 2.1 second LCP" class="has-border-color has-accent-6-border-color wp-image-36155 not-transparent" style="--dominant-color: #f5f8f7; border-width:1px;box-shadow:var(--wp--preset--shadow--natural)" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-score-cover-block-with-image-prioritizer.png?resize=700%2C468&amp;ssl=1 700w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-score-cover-block-with-image-prioritizer.png?resize=300%2C201&amp;ssl=1 300w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-score-cover-block-with-image-prioritizer.png?resize=768%2C513&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-score-cover-block-with-image-prioritizer.png?resize=150%2C100&amp;ssl=1 150w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-score-cover-block-with-image-prioritizer.png?w=1454&amp;ssl=1 1454w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button><figcaption class="wp-element-caption">After</figcaption></figure>
</div>
</div>



<p>Surely nothing can improve LCP more than what was achieved here with the Image Prioritizer plugin, right? Read on.</p>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading" id="speculative-loading">Speculative Loading</h3>



<figure class="wp-block-embed is-type-wp-embed is-provider-plugin-directory wp-block-embed-plugin-directory"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="WREIcgRthU"><a href="https://wordpress.org/plugins/speculation-rules/">Speculative Loading</a></blockquote><iframe loading="lazy" class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  title="&#8220;Speculative Loading&#8221; &#8212; Plugin Directory" src="https://wordpress.org/plugins/speculation-rules/embed/#?secret=gWc1Ryydga#?secret=WREIcgRthU" data-secret="WREIcgRthU" width="500" height="282" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>



<p>The <a href="https://wordpress.org/plugins/speculation-rules/">Speculative Loading</a> plugin is the first discussed here not specifically focused on improving LCP for images, although they do benefit. This was a feature plugin actually to bring the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Speculation_Rules_API">Speculation Rules API</a> to WordPress core, which was <a href="https://make.wordpress.org/core/2025/03/06/speculative-loading-in-6-8/">merged</a> in 6.8. This API allows pages to either:</p>



<ul class="wp-block-list">
<li>Prefetch a link, reducing TTFB to zero.</li>



<li>Prerender a link, potentially reducing LCP to zero.</li>
</ul>



<p>As such, Speculative Loading is somewhat cheating at performance because you can&#8217;t get any faster at loading something than to have it already loaded.</p>



<p>The degree by which TTFB and LCP are improved is largely dependent on the “eagerness” of the speculation. There are three main eagerness values for when speculation starts:</p>



<ul class="wp-block-list">
<li>Conservative: when you pointer-down on a link.</li>



<li>Moderate: when you hover over a link (or <a href="https://issues.chromium.org/issues/372053392">soon</a> on mobile when a link is in the viewport).</li>



<li>Eager: right away without any user interaction. </li>
</ul>



<p>For the initial core merge, the default cautious configuration was to use prefetch with conservative eagerness. Conservative eagerness was to avoid unused speculations which can overly tax under-powered servers, and prefetching was to avoid potential compatibility issues with prerendering, such as with analytics or ads.</p>



<p>Here&#8217;s the impact that the various configurations of Speculative Loading have on LCP:</p>



<div id="play-all-button-row" class="wp-block-group is-content-justification-center is-nowrap is-layout-flex wp-container-core-group-is-layout-23441af8 wp-block-group-is-layout-flex block-visibility-hide-small-screen">
<div class="wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex">
<div class="wp-block-button"><button type="button" class="wp-block-button__link has-text-align-center wp-element-button">Play All</button></div>
</div>
</div>



<noscript>
  <style>
    #play-all-button-row { display:none }
  </style>
</noscript>
<script nonce="OvZcGcCNnoXpw62JEGhwY0" type="module">
document.querySelector('#play-all-button-row button').addEventListener('click', () => {
	document.querySelectorAll('#speculative-loading-videos video').forEach( ( /** @type HTMLVideoElement */ video) => {
		video.currentTime = 0;
		video.play();
	});
});
</script>



<div class="wp-block-columns alignwide is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex" id="speculative-loading-videos">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><strong>No Speculation</strong></p>



<p class="has-text-align-center has-text-color has-link-color has-xx-large-font-size wp-elements-777994579971ce640098eb4e8cb65b05" style="color:#388e3c"><strong>2.17 s</strong></p>



<figure class="wp-block-video aligncenter"><video height="1080" style="aspect-ratio: 914 / 1080;aspect-ratio: 914 / 1080;" width="914" controls muted poster="https://weston.ruter.net/wp-content/uploads/2025/08/speculative-loading-0-poster-source.png" src="https://weston.ruter.net/wp-content/uploads/2025/08/speculative-loading-1-none-8s.mp4" playsinline></video><figcaption class="wp-element-caption">(experience in 6.7)</figcaption></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><strong>Conservative Prefetch</strong></p>



<p class="has-text-align-center has-text-color has-link-color has-xx-large-font-size wp-elements-147cc81b04c8bc40516dd723463d23b5" style="color:#388e3c"><strong>2.12 s</strong></p>



<figure class="wp-block-video aligncenter"><video height="1080" style="aspect-ratio: 914 / 1080;aspect-ratio: 914 / 1080;" width="914" controls muted poster="https://weston.ruter.net/wp-content/uploads/2025/08/speculative-loading-0-poster-source.png" src="https://weston.ruter.net/wp-content/uploads/2025/08/speculative-loading-2-conservative-prefetch-8s.mp4" playsinline></video><figcaption class="wp-element-caption">2.3% reduction in LCP<br>(default as of WP 6.8)</figcaption></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><strong>Moderate Prefetch</strong></p>



<p class="has-text-align-center has-text-color has-link-color has-xx-large-font-size wp-elements-0f2991c2ac028f6dc5e22b4a3ca3ef5d" style="color:#388e3c"><strong>1.04 s</strong></p>



<figure class="wp-block-video aligncenter"><video height="1080" style="aspect-ratio: 914 / 1080;aspect-ratio: 914 / 1080;" width="914" controls muted poster="https://weston.ruter.net/wp-content/uploads/2025/08/speculative-loading-0-poster-source.png" src="https://weston.ruter.net/wp-content/uploads/2025/08/speculative-loading-3-moderate-prefetch-8s.mp4" playsinline></video><figcaption class="wp-element-caption">52.1% reduction in LCP</figcaption></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><strong>Moderate Prerender</strong></p>



<p class="has-text-align-center has-text-color has-link-color has-xx-large-font-size wp-elements-6f7ec60ce81ca49e08396be48dbe073c" style="color:#388e3c"><strong>0.04 s</strong></p>



<figure class="wp-block-video aligncenter"><video height="1080" style="aspect-ratio: 914 / 1080;aspect-ratio: 914 / 1080;" width="914" controls muted poster="https://weston.ruter.net/wp-content/uploads/2025/08/speculative-loading-0-poster-source.png" src="https://weston.ruter.net/wp-content/uploads/2025/08/speculative-loading-4-moderate-prerender-8s.mp4" playsinline></video><figcaption class="wp-element-caption">98.2% reduction in LCP</figcaption></figure>
</div>
</div>



<p>Navigation with prerendering results in a practically instantaneous page load with a near zero LCP! In all these cases the LCP is still considered “good” at being less than 2.5 seconds, but just because something is good doesn&#8217;t mean it can&#8217;t be better!</p>



<p>Note that the test page here adds a 1 second TTFB via <code>sleep(1)</code>. This reflects a fairly typical server response time considering that only a <a href="https://httparchive.org/reports/techreport/tech?tech=ALL%2CWordPress&amp;geo=ALL&amp;rank=ALL&amp;good-cwv-over-time=TTFB#comparison-good-cwvs">quarter</a> of WordPress sites have a good TTFB passing rate, which is 800 ms and faster.</p>



<p><em>Props to <a href="https://felix-arntz.me/">Felix Arntz</a> for spearheading this feature and landing it in core.</em></p>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading" id="view-transitions">View Transitions</h3>



<figure class="wp-block-embed is-type-wp-embed is-provider-plugin-directory wp-block-embed-plugin-directory"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="TcVqBkmTbG"><a href="https://wordpress.org/plugins/view-transitions/">View Transitions</a></blockquote><iframe loading="lazy" class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  title="&#8220;View Transitions&#8221; &#8212; Plugin Directory" src="https://wordpress.org/plugins/view-transitions/embed/#?secret=59ZHngXoNi#?secret=TcVqBkmTbG" data-secret="TcVqBkmTbG" width="500" height="282" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>



<p>As described in the previous example, page navigations with Speculative Loading can be nearly instant with prerendering. This is great, but it&#8217;s almost too good. The navigation can feel so instant as to be abrupt. There can also be a white flicker between the page loads. To help in part with having too much of a good thing, the newest plugin to be featured in Performance Lab is <a href="https://wordpress.org/plugins/view-transitions/">View Transitions</a>. There is a new web platform feature for <a href="https://developer.chrome.com/docs/web-platform/view-transitions/cross-document">cross-document view transitions for multi-page applications</a>, and this plugin brings these smooth page navigation animations to WordPress. With Speculative Loading and View Transitions, navigating around a regular multi-page WordPress site can feel as fluid as a single-page app (and without all the implementation complexity).</p>



<p>Take a look at the impact on the user experience when navigating between the homepage and a blog post:</p>



<div class="wp-block-columns is-not-stacked-on-mobile is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-video aligncenter"><video height="1080" style="aspect-ratio: 756 / 1080;aspect-ratio: 756 / 1080;" width="756" controls loop muted poster="https://weston.ruter.net/wp-content/uploads/2025/08/view-transitions-poster.jpg" src="https://weston.ruter.net/wp-content/uploads/2025/08/view-transitions-disabled-with-navigation-via-links.mp4" playsinline></video><figcaption class="wp-element-caption">Without View Transitions</figcaption></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-video aligncenter"><video height="1080" style="aspect-ratio: 756 / 1080;aspect-ratio: 756 / 1080;" width="756" controls loop muted poster="https://weston.ruter.net/wp-content/uploads/2025/08/view-transitions-poster.jpg" src="https://weston.ruter.net/wp-content/uploads/2025/08/view-transitions-enabled-with-navigation-via-links.mp4" playsinline></video><figcaption class="wp-element-caption">With View Transitions</figcaption></figure>
</div>
</div>



<p>Note that these view transitions apply not only when navigating via links, but they also apply when navigating with the back/forward buttons in the browser:</p>



<div class="wp-block-columns is-not-stacked-on-mobile is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-video aligncenter"><video height="1080" style="aspect-ratio: 722 / 1080;aspect-ratio: 722 / 1080;" width="722" controls loop muted poster="https://weston.ruter.net/wp-content/uploads/2025/08/view-transitions-via-bfcache-poster.jpg" src="https://weston.ruter.net/wp-content/uploads/2025/08/view-transitions-disabled-with-navigation-via-bfcache.mp4" playsinline></video><figcaption class="wp-element-caption">Without View Transitions</figcaption></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-video aligncenter"><video height="1080" style="aspect-ratio: 722 / 1080;aspect-ratio: 722 / 1080;" width="722" controls loop muted poster="https://weston.ruter.net/wp-content/uploads/2025/08/view-transitions-via-bfcache-poster.jpg" src="https://weston.ruter.net/wp-content/uploads/2025/08/view-transitions-enabled-with-navigation-via-bfcache.mp4" playsinline></video><figcaption class="wp-element-caption">With View Transitions</figcaption></figure>
</div>
</div>



<p>Nevertheless, as nice as these cross-document view transitions are, do note that there is no LCP improvement to using them. As referenced previously with Image Placeholders, the View Transitions plugin provides a non-performance user experience improvement. So don&#8217;t expect to find any difference in your Lighthouse scores or LCP passing rates with this plugin.</p>



<p><em>Props again to <a href="https://felix-arntz.me/">Felix Arntz</a> for spearheading this feature plugin.</em></p>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading" id="nocache-bfcache">Instant Back/Forward</h3>



<figure class="wp-block-embed is-type-wp-embed is-provider-plugin-directory wp-block-embed-plugin-directory"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="N5iEnHJE10"><a href="https://wordpress.org/plugins/nocache-bfcache/">Instant Back/Forward</a></blockquote><iframe loading="lazy" class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  title="&#8220;Instant Back/Forward&#8221; &#8212; Plugin Directory" src="https://wordpress.org/plugins/nocache-bfcache/embed/#?secret=5Kd9ttK4kO#?secret=N5iEnHJE10" data-secret="N5iEnHJE10" width="500" height="282" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>



<p>Originally, the <a href="https://wordpress.org/plugins/nocache-bfcache/">Instant Back/Forward</a> plugin (née “No-cache BFCache”) was part of the “beyond” part of my talk because it wasn&#8217;t among the plugins featured by Performance Lab. However, this is no longer the case since <a href="https://github.com/WordPress/performance/releases/tag/2025-08-25">v4.0.0</a>. In the previous section about Speculative Loading, I showed how prerendering enabled near instant page loads with practically zero LCP. But there is a much older browser technology for instantaneous page navigations: the <a href="https://web.dev/articles/bfcache">back-forward cache</a> (bfcache). This was also depicted above in the back/forward navigation videos with view transitions.</p>



<p>I wrote up a <a href="https://weston.ruter.net/2025/07/23/instant-back-forward-navigations-in-wordpress/">blog post</a> already all about bfcache and this plugin:</p>



<figure class="wp-block-embed is-type-wp-embed is-provider-weston-ruter wp-block-embed-weston-ruter"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="Yc4BQ3eqPC"><a href="https://weston.ruter.net/2025/07/23/instant-back-forward-navigations-in-wordpress/">Instant Back/Forward Navigations in WordPress</a></blockquote><iframe loading="lazy" class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  title="&#8220;Instant Back/Forward Navigations in WordPress&#8221; &#8212; Weston Ruter" src="https://weston.ruter.net/2025/07/23/instant-back-forward-navigations-in-wordpress/embed/#?secret=TrfGrpSTH9#?secret=Yc4BQ3eqPC" data-secret="Yc4BQ3eqPC" width="500" height="282" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>



<p>To recap, webpages are generally not eligible for bfcache when they are served with <code>Cache-Control: no-store</code>. This header is sent when a user is logged-in and often on e-commerce sites for the shopping cart, checkout, and account pages. While it importantly prevents such pages from being cached by proxies, it also prevents the browser from storing pages in bfcache. This plugin removes the <code>no-store</code> directive. In its place, it ensures that the <code>private</code> directive is sent to prevent proxies from caching the response; also, to ensure preserve privacy after logging out, it includes logic to invalidate pages from the bfcache so they cannot be re-accessed.</p>



<p>What follows is an example of a site running Twenty Twenty-Five with the BuddyPress plugin and Slow 4G network emulation. After entering an activity status update, I navigate from the Personal tab to the Mentions and Favorites tabs. Then I use the back button to go back to the Personal tab. Without bfcache, navigating back from the Favorites tab to the Personal tab is very slow since (1) the browser has to re-fetch the HTML from the server, and (2) the DOM has to be completely reconstructed. Without bfcache, there is also the unfortunate result that the drafted status update is lost, since the form field was re-constructed with JavaScript. In contrast, when bfcache is enabled, navigating to the previous tabs is instant, and the DOM is preserved with each navigation, resulting in the drafted status update being kept intact:</p>



<div class="wp-block-columns is-not-stacked-on-mobile is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-video aligncenter"><video height="1080" style="aspect-ratio: 788 / 1080;aspect-ratio: 788 / 1080;" width="788" controls muted poster="https://weston.ruter.net/wp-content/uploads/2025/08/buddypress-poster.jpg" src="https://weston.ruter.net/wp-content/uploads/2025/08/buddypress-sans-bfcache.mp4" playsinline></video><figcaption class="wp-element-caption">Without bfcache</figcaption></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-video aligncenter"><video height="1080" style="aspect-ratio: 788 / 1080;aspect-ratio: 788 / 1080;" width="788" controls poster="https://weston.ruter.net/wp-content/uploads/2025/08/buddypress-poster.jpg" src="https://weston.ruter.net/wp-content/uploads/2025/08/buddypress-with-bfcache.mp4"></video><figcaption class="wp-element-caption">With bfcache</figcaption></figure>
</div>
</div>



<p>Without bfcache, the back navigation has an LCP of <strong>1.41 seconds</strong> whereas with bfcache the LCP is <strong>0.02 seconds</strong>: nearly instantaneous. </p>



<p>There are other <a href="https://web.dev/articles/bfcache#optimize">reasons</a> why pages may be ineligible for bfcache than the <code>no-store</code> directive, but it is one of the most common causes. It&#8217;s very important to try to preserve bfcache eligibility because back/forward navigations are <a href="https://web.dev/articles/bfcache#:~:text=Chrome%20usage%20data%20shows%20that%201%20in%2010%20navigations%20on%20desktop%20and%201%20in%205%20on%20mobile%20are%20either%20back%20or%20forward.">very common</a> on the web:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Chrome usage data shows that 1 in 10 navigations on desktop and 1 in 5 on mobile are either back or forward.</p>
</blockquote>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading" id="script-module-deprioritization">Script Module Deprioritization</h3>



<p>Moving on from instant page loads with Speculative Loading and bfcache, another way to shave off milliseconds on the LCP metric is to reduce network contention for loading the LCP element resource (e.g. an image). Consider a template with an Image block and a Navigation block, where the Image block has a lightbox and the Navigation block expands on mobile. These blocks use the Interactivity API which involves adding script modules to the page with the necessary logic. As noted previously, one of the key design principles of the Interactivity API is <a href="https://developer.wordpress.org/block-editor/reference-guides/interactivity-api/core-concepts/server-side-rendering/">server-side rendering</a>. This means that by design the Navigation block and the Image block do <em>not</em> need their script modules in the critical rendering path.</p>



<p>It turns out that these script modules are currently loaded with high priority because the browser doesn&#8217;t know they aren&#8217;t critical. So they compete with the loading of critical resources, like the LCP image, even though script modules aren&#8217;t render blocking.</p>



<p>I&#8217;ve written a <a href="https://weston.ruter.net/2025/05/26/improve-lcp-by-deprioritizing-interactivity-api-script-modules/">separate post</a> all about this problem and the solution:</p>



<figure class="wp-block-embed is-type-wp-embed is-provider-weston-ruter wp-block-embed-weston-ruter"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="77hxsnLQlU"><a href="https://weston.ruter.net/2025/05/26/improve-lcp-by-deprioritizing-interactivity-api-script-modules/">Improve LCP by Deprioritizing  Script Modules from the Interactivity API</a></blockquote><iframe loading="lazy" class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  title="&#8220;Improve LCP by Deprioritizing  Script Modules from the Interactivity API&#8221; &#8212; Weston Ruter" src="https://weston.ruter.net/2025/05/26/improve-lcp-by-deprioritizing-interactivity-api-script-modules/embed/#?secret=BBStIdMyss#?secret=77hxsnLQlU" data-secret="77hxsnLQlU" width="500" height="282" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>



<p>To summarize, there are two ways to prevent script modules from delaying the loading of critical resources:</p>



<ol class="wp-block-list">
<li>Add <code>fetchpriority=​low</code> to the <code>SCRIPT</code> module tags and the <code>modulepreload</code> <code>LINK</code>.</li>



<li>Move the <code>SCRIPT</code> tags to the end of the <code>BODY</code> (the footer).</li>
</ol>



<p>Here are the results of these optimizations on an emulated broadband connection with an <code>IMG</code> as the LCP element:</p>



<figure class="wp-block-table benchmark-web-vitals"><table><thead><tr><th>Metric</th><th>Before</th><th>After</th><th>Diff (ms)</th><th>Diff (%)</th></tr></thead><tbody><tr><td>FCP</td><td>137.0</td><td>137.2</td><td>+0.2</td><td>+0.1%</td></tr><tr><td>LCP</td><td>406.0</td><td>368.8</td><td>-37.2</td><td><mark style="background-color:#FFEE58" class="has-inline-color"><strong>-9.2%</strong></mark></td></tr><tr><td>TTFB</td><td>33.7</td><td>33.6</td><td>-0.1</td><td>-0.1%</td></tr><tr><td>LCP-TTFB</td><td>371.7</td><td>336.0</td><td>-35.7</td><td>-9.6%</td></tr></tbody></table></figure>



<p>This is a healthy LCP improvement, more impactful than using the Modern Image Formats with the AVIF format in my testing above. There are two plugins available on GitHub which implement these optimizations while waiting for them to be available in core:</p>



<ul class="wp-block-list">
<li><a href="https://github.com/westonruter/script-fetchpriority-low">Script Fetch Priority Low</a> (cf. <a href="https://core.trac.wordpress.org/ticket/61734" title="Add the ability to handle &quot;fetchpriority&quot; to ES Modules and Import Maps">#61734</a>)</li>



<li><a href="https://github.com/westonruter/script-modules-in-footer">Script Modules in Footer</a> (cf. <a href="https://core.trac.wordpress.org/ticket/63486" title="Script modules should support being printed in the footer the same as classic scripts">#63486</a>)</li>
</ul>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading" id="minified-css-inlining">Minified CSS Inlining</h3>



<p>The final optimization I&#8217;m analyzing is the impact of eliminating render-blocking external stylesheets. With JavaScript, adding <code>defer</code> to a <code>SCRIPT</code> is an easy way to prevent them from blocking rendering (assuming they can be deferred). However, this is not so easy to do with external stylesheets. CSS is always render-blocking because otherwise there is a flash of unstyled content (FOUC). The web platform does not (<a href="https://scottjehl.com/posts/async-css-already/">currently</a>) provide an official way to opt in to <a href="https://www.filamentgroup.com/lab/load-css-simpler/">async CSS</a>. Instead, the best way to handle this is to inline the CSS in <code>STYLE</code> tags (at least for the critical CSS).</p>



<p>On a vanilla WordPress install when loading the Sample Page, where the LCP element is text, there are two render-blocking stylesheets:</p>



<ul class="wp-block-list">
<li>The Navigation block&#8217;s <code>style.min.css</code></li>



<li>The Twenty Twenty-Five theme&#8217;s <code>style.css</code></li>
</ul>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bcdfd4abc&quot;}" data-wp-interactive="core/image" data-wp-key="6975bcdfd4abc" class="wp-block-image aligncenter size-large wp-lightbox-container"><img data-recalc-dims="1" data-dominant-color="f2f3f6" data-has-transparency="false" style="--dominant-color: #f2f3f6;" loading="lazy" decoding="async" width="700" height="243" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 645px) 100vw, 645px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/twentytwentyfive-render-blocking-stylesheets.png?resize=700%2C243&#038;ssl=1" alt="" class="wp-image-36207 not-transparent" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/twentytwentyfive-render-blocking-stylesheets-scaled.png?resize=700%2C243&amp;ssl=1 700w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/twentytwentyfive-render-blocking-stylesheets-scaled.png?resize=300%2C104&amp;ssl=1 300w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/twentytwentyfive-render-blocking-stylesheets-scaled.png?resize=768%2C266&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/twentytwentyfive-render-blocking-stylesheets-scaled.png?resize=1536%2C533&amp;ssl=1 1536w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/twentytwentyfive-render-blocking-stylesheets-scaled.png?resize=2048%2C711&amp;ssl=1 2048w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/twentytwentyfive-render-blocking-stylesheets-scaled.png?resize=150%2C52&amp;ssl=1 150w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button><figcaption class="wp-element-caption">External stylesheets in the DevTools network panel.</figcaption></figure>



<p>Despite these render-blocking stylesheets, Lighthouse is giving the page a 100 performance score. But as I&#8217;ve said before, just because you have a 100 score in Lighthouse, this doesn&#8217;t mean you can do more. Even with a perfect Lighthouse score, there is actually an audit that is pointing out the performance problem: <a href="https://developer.chrome.com/docs/lighthouse/performance/render-blocking-resources">Eliminate render-blocking resources</a>.</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bcdfd5630&quot;}" data-wp-interactive="core/image" data-wp-key="6975bcdfd5630" class="wp-block-image size-large has-custom-border wp-lightbox-container"><img data-recalc-dims="1" data-dominant-color="f4f5f7" data-has-transparency="false" loading="lazy" decoding="async" width="700" height="372" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 645px) 100vw, 645px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-eliminate-render-blocking-resources.png?resize=700%2C372&#038;ssl=1" alt="Eliminate render-blocking resources — Est savings of 0 ms.

Resources are blocking the first paint of your page. Consider delivering critical JS/CSS inline and deferring all non-critical JS/styles. Learn how to eliminate render-blocking resources. FCP | LCP

There are a number of WordPress plugins that can help you inline critical assets or defer less important resources. Beware that optimizations provided by these plugins may break features of your theme or plugins, so you will likely need to make code changes." class="has-border-color has-accent-6-border-color wp-image-36208 not-transparent" style="--dominant-color: #f4f5f7; border-width:1px;box-shadow:var(--wp--preset--shadow--natural)" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-eliminate-render-blocking-resources.png?resize=700%2C372&amp;ssl=1 700w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-eliminate-render-blocking-resources.png?resize=300%2C159&amp;ssl=1 300w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-eliminate-render-blocking-resources.png?resize=768%2C408&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-eliminate-render-blocking-resources.png?resize=1536%2C817&amp;ssl=1 1536w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-eliminate-render-blocking-resources.png?resize=2048%2C1089&amp;ssl=1 2048w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/lighthouse-eliminate-render-blocking-resources.png?resize=150%2C80&amp;ssl=1 150w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<p>It&#8217;s strange that this audit has an overall estimated savings of zero milliseconds, but for the theme&#8217;s stylesheet it shows an estimated savings of 150 milliseconds.</p>



<p>To inline these two stylesheets to prevent them from being render-blocking, what is needed is to:</p>



<ol class="wp-block-list">
<li>Opt in to inline the (minified) theme&#8217;s <code>style.css</code>.</li>



<li>Increase the <code>styles_inline_size_limit</code>.</li>
</ol>



<p>To inline Twenty Twenty-Five&#8217;s stylesheet, all that is required is to add the <code>path</code> data for where the registered style is located on the filesystem. This can be done as simply as follows:</p>


<pre class="wp-block-code"><span><code class="hljs language-php">add_action(
	<span class="hljs-string">'wp_enqueue_scripts'</span>,
	<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">()</span>: <span class="hljs-title">void</span> </span>{
		wp_style_add_data(
			<span class="hljs-string">'twentytwentyfive-style'</span>,
			<span class="hljs-string">'path'</span>,
			get_parent_theme_file_path( <span class="hljs-string">'style.css'</span> )
		);
	},
	<span class="hljs-number">20</span>
);</code></span></pre>


<p>However, since the stylesheet is not yet minified (cf. <a title="Bundled themes: Stylesheets should be minified" href="https://core.trac.wordpress.org/ticket/63012">#63012</a>), you can hack in runtime minification using a plugin like <a href="https://gist.github.com/westonruter/09e553a7b66d1a2e68cd5a9ed351c59b">Twenty Twenty-Five Stylesheet Inlining</a>. This plugin is currently just in a Gist since I hope this will land soon in core for 6.9 via <a title="Bundled themes: Stylesheets for block themes are missing path data for inlining" href="https://core.trac.wordpress.org/ticket/63007">#63007</a>.</p>



<p>To increase the limit for inline CSS, all that is needed is a simple filter. The default limit is 20 KB which seems low considering the inline CSS limit for an AMP page is 75 KB. To increase the limit to 30 KB which allows enough room for the Navigation block&#8217;s relatively stylesheet to be inlined, you can use this PHP code:</p>


<pre class="wp-block-code"><span><code class="hljs language-php">add_filter(
	<span class="hljs-string">'styles_inline_size_limit'</span>,
	<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">()</span>: <span class="hljs-title">int</span> </span>{
		<span class="hljs-keyword">return</span> <span class="hljs-number">30000</span>;
	}
);</code></span></pre>


<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>Plugin used for benchmarking below</summary><pre class="wp-block-code"><span><code class="hljs language-php"><span class="hljs-meta">&lt;?php</span>
<span class="hljs-comment">/**
 * Plugin Name: Increase Styles Inline Size Limit (styles_inline_size_limit)
 * Author: Weston Ruter
 * Update URI: false
 */</span>
add_filter(
	<span class="hljs-string">'styles_inline_size_limit'</span>,
	<span class="hljs-keyword">static</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">()</span>: <span class="hljs-title">int</span> </span>{
		$limit = <span class="hljs-number">-1</span>;
		<span class="hljs-keyword">if</span> ( <span class="hljs-keyword">isset</span>( $_GET&#91;<span class="hljs-string">'styles_inline_size_limit'</span>] ) ) {
			$limit = (int) $_GET&#91;<span class="hljs-string">'styles_inline_size_limit'</span>];
		}
		<span class="hljs-keyword">if</span> ( $limit &lt; <span class="hljs-number">0</span> ) {
			$limit = <span class="hljs-number">75000</span>;
		}
		<span class="hljs-keyword">return</span> $limit;
	}
);</code></span></pre></details>



<p>Increasing this limit in core is being tracked in <a title="Increase styles_inline_size_limit from 20,000 bytes" href="https://core.trac.wordpress.org/ticket/63018">#63018</a>. We still need to determine the optimal threshold for inlining, weighing against the benefits of serving stylesheets from the browser cache for subsequent page navigations.</p>



<p>As for the performance impact of inlining these stylesheets, here are the results for the loading Sample Page on an emulated Fast 4G connection:</p>



<figure class="wp-block-table benchmark-web-vitals"><table><thead><tr><th>Metric</th><th>Before</th><th>After</th><th>Diff (ms)</th><th>Diff (%)</th></tr></thead><tbody><tr><td>FCP</td><td>409.1</td><td>228.4</td><td>-180.7</td><td>-44.2%</td></tr><tr><td>LCP</td><td>510.0</td><td>325.4</td><td>-184.6</td><td><mark style="background-color:#FFEE58" class="has-inline-color"><strong>-36.2%</strong></mark></td></tr><tr><td>TTFB</td><td>43.3</td><td>43.8</td><td>+0.6</td><td>+1.3%</td></tr><tr><td>LCP-TTFB</td><td>466.5</td><td>281.2</td><td>-185.4</td><td>-39.7%</td></tr></tbody></table></figure>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>Benchmark command</summary><pre class="wp-block-code"><span><code class="hljs language-javascript">npm run research -- benchmark-web-vitals \
	--url=<span class="hljs-string">"https://wcus-perf-talk-demo.local/sample-page/?enable_plugins=twentytwentyfive-stylesheet-inlining"</span> \
	--url=<span class="hljs-string">"https://wcus-perf-talk-demo.local/sample-page/?enable_plugins=twentytwentyfive-stylesheet-inlining,increase-styles-inline-size-limit.php&amp;styles_inline_size_limit=30000"</span> \
	--number=<span class="hljs-number">50</span> \
	--network-conditions=<span class="hljs-string">"Fast 4G"</span> \
	--emulate-device=<span class="hljs-string">"Moto G4"</span> \
	--diff \
	--output=md</code></span></pre></details>



<p>This decreases the LCP by over a third! </p>



<p>And here are the results when emulating a Slow 3G connection:</p>



<figure class="wp-block-table benchmark-web-vitals"><table><thead><tr><th>Metric</th><th>Before</th><th>After</th><th>Diff (ms)</th><th>Diff (%)</th></tr></thead><tbody><tr><td>FCP</td><td>4206.5</td><td>2276.0</td><td>-1930.5</td><td>-45.9%</td></tr><tr><td>LCP</td><td>4308.3</td><td>2384.6</td><td>-1923.7</td><td><mark style="background-color:#FFEE58" class="has-inline-color"><strong>-44.7%</strong></mark></td></tr><tr><td>TTFB</td><td>42.6</td><td>45.5</td><td>+2.85</td><td>+6.7%</td></tr><tr><td>LCP-TTFB</td><td>4265.9</td><td>2339.7</td><td>-1926.3</td><td>-45.2%</td></tr></tbody></table></figure>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>Benchmark command</summary><pre class="wp-block-code"><span><code class="hljs language-javascript">npm run research -- benchmark-web-vitals \
	--url=<span class="hljs-string">"https://wcus-perf-talk-demo.local/sample-page/?enable_plugins=twentytwentyfive-stylesheet-inlining"</span> \
	--url=<span class="hljs-string">"https://wcus-perf-talk-demo.local/sample-page/?enable_plugins=twentytwentyfive-stylesheet-inlining,increase-styles-inline-size-limit.php&amp;styles_inline_size_limit=30000"</span> \
	--number=<span class="hljs-number">50</span> \
	--network-conditions=<span class="hljs-string">"Slow 3G"</span> \
	--emulate-device=<span class="hljs-string">"Moto G4"</span> \
	--diff \
	--output=md</code></span></pre></details>



<p>A 44.4% reduction in LCP is on par with the largest improvements achieved by the Image Prioritizer plugin in my evaluations here. This means that on a Slow 3G connection, the LCP goes from <strong><mark style="background-color:rgba(0, 0, 0, 0);color:#cc0000" class="has-inline-color">poor</mark></strong> at 4.31 seconds to <strong><mark style="background-color:rgba(0, 0, 0, 0);color:#388e3c" class="has-inline-color">good</mark></strong> at 2.38 seconds.</p>



<h2 class="wp-block-heading" id="whats-next">What&#8217;s Next</h2>



<p>My hope is that several of these improvements will land later this year in WordPress core. Some of them are <a href="https://make.wordpress.org/core/2025/07/28/roadmap-to-6-9/#performance-improvements">tracked</a> in the <a href="https://make.wordpress.org/core/2025/07/28/roadmap-to-6-9/">Roadmap to 6.9</a>:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Planned performance improvements include improving Data Views performance <a href="https://github.com/WordPress/gutenberg/issues/70789">by supporting partial entity fetching and smart field resolution</a>, <a href="https://core.trac.wordpress.org/ticket/61734">adding the ability to handle “fetchpriority” to ES Modules and Import Maps</a>, <a href="https://core.trac.wordpress.org/ticket/63636">standardizing output buffering</a> so developers can hook into a unified filter and manipulate the entire rendered HTML after it’s generated but before it’s sent to the browser (e.g. for page caches and performance optimizations), implementing <a href="https://core.trac.wordpress.org/ticket/63636">instant page navigations from browser history via bfcache</a> even when pages are flagged with “nocache” such as when users are logged in, and <a href="https://core.trac.wordpress.org/ticket/63007#comment:31">stylesheet improvements around minification and inlining</a>.</p>
</blockquote>



<p>You can <a href="https://make.wordpress.org/performance/handbook/get-involved/">get involved</a> with the Core Performance Team to help make this happen!</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="has-medium-font-size">Where I&#8217;ve shared this, if you want to discuss or boost:</p>



<ul class="wp-block-social-links is-layout-flex wp-block-social-links-is-layout-flex"><li class="wp-social-link wp-social-link-linkedin  wp-block-social-link"><a href="https://www.linkedin.com/posts/westonruter_the-site-speed-frontier-with-performance-activity-7366592585110310914-fAWv" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M19.7,3H4.3C3.582,3,3,3.582,3,4.3v15.4C3,20.418,3.582,21,4.3,21h15.4c0.718,0,1.3-0.582,1.3-1.3V4.3 C21,3.582,20.418,3,19.7,3z M8.339,18.338H5.667v-8.59h2.672V18.338z M7.004,8.574c-0.857,0-1.549-0.694-1.549-1.548 c0-0.855,0.691-1.548,1.549-1.548c0.854,0,1.547,0.694,1.547,1.548C8.551,7.881,7.858,8.574,7.004,8.574z M18.339,18.338h-2.669 v-4.177c0-0.996-0.017-2.278-1.387-2.278c-1.389,0-1.601,1.086-1.601,2.206v4.249h-2.667v-8.59h2.559v1.174h0.037 c0.356-0.675,1.227-1.387,2.526-1.387c2.703,0,3.203,1.779,3.203,4.092V18.338z"></path></svg><span class="wp-block-social-link-label screen-reader-text">LinkedIn</span></a></li>

<li class="wp-social-link wp-social-link-bluesky  wp-block-social-link"><a href="https://bsky.app/profile/weston.ruter.net/post/3lxg2haotcs2c" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M6.3,4.2c2.3,1.7,4.8,5.3,5.7,7.2.9-1.9,3.4-5.4,5.7-7.2,1.7-1.3,4.3-2.2,4.3.9s-.4,5.2-.6,5.9c-.7,2.6-3.3,3.2-5.6,2.8,4,.7,5.1,3,2.9,5.3-5,5.2-6.7-2.8-6.7-2.8,0,0-1.7,8-6.7,2.8-2.2-2.3-1.2-4.6,2.9-5.3-2.3.4-4.9-.3-5.6-2.8-.2-.7-.6-5.3-.6-5.9,0-3.1,2.7-2.1,4.3-.9h0Z"></path></svg><span class="wp-block-social-link-label screen-reader-text">Bluesky</span></a></li>

<li class="wp-social-link wp-social-link-twitter  wp-block-social-link"><a href="https://x.com/westonruter/status/1960827200384721188" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M22.23,5.924c-0.736,0.326-1.527,0.547-2.357,0.646c0.847-0.508,1.498-1.312,1.804-2.27 c-0.793,0.47-1.671,0.812-2.606,0.996C18.324,4.498,17.257,4,16.077,4c-2.266,0-4.103,1.837-4.103,4.103 c0,0.322,0.036,0.635,0.106,0.935C8.67,8.867,5.647,7.234,3.623,4.751C3.27,5.357,3.067,6.062,3.067,6.814 c0,1.424,0.724,2.679,1.825,3.415c-0.673-0.021-1.305-0.206-1.859-0.513c0,0.017,0,0.034,0,0.052c0,1.988,1.414,3.647,3.292,4.023 c-0.344,0.094-0.707,0.144-1.081,0.144c-0.264,0-0.521-0.026-0.772-0.074c0.522,1.63,2.038,2.816,3.833,2.85 c-1.404,1.1-3.174,1.756-5.096,1.756c-0.331,0-0.658-0.019-0.979-0.057c1.816,1.164,3.973,1.843,6.29,1.843 c7.547,0,11.675-6.252,11.675-11.675c0-0.178-0.004-0.355-0.012-0.531C20.985,7.47,21.68,6.747,22.23,5.924z"></path></svg><span class="wp-block-social-link-label screen-reader-text">Twitter</span></a></li>

<li class="wp-social-link wp-social-link-mastodon  wp-block-social-link"><a href="https://mastodon.social/@westonruter/115103019556229139" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M23.193 7.879c0-5.206-3.411-6.732-3.411-6.732C18.062.357 15.108.025 12.041 0h-.076c-3.068.025-6.02.357-7.74 1.147 0 0-3.411 1.526-3.411 6.732 0 1.192-.023 2.618.015 4.129.124 5.092.934 10.109 5.641 11.355 2.17.574 4.034.695 5.535.612 2.722-.15 4.25-.972 4.25-.972l-.09-1.975s-1.945.613-4.129.539c-2.165-.074-4.449-.233-4.799-2.891a5.499 5.499 0 0 1-.048-.745s2.125.52 4.817.643c1.646.075 3.19-.097 4.758-.283 3.007-.359 5.625-2.212 5.954-3.905.517-2.665.475-6.507.475-6.507zm-4.024 6.709h-2.497V8.469c0-1.29-.543-1.944-1.628-1.944-1.2 0-1.802.776-1.802 2.312v3.349h-2.483v-3.35c0-1.536-.602-2.312-1.802-2.312-1.085 0-1.628.655-1.628 1.944v6.119H4.832V8.284c0-1.289.328-2.313.987-3.07.68-.758 1.569-1.146 2.674-1.146 1.278 0 2.246.491 2.886 1.474L12 6.585l.622-1.043c.64-.983 1.608-1.474 2.886-1.474 1.104 0 1.994.388 2.674 1.146.658.757.986 1.781.986 3.07v6.304z"/></svg><span class="wp-block-social-link-label screen-reader-text">Mastodon</span></a></li>

<li class="wp-social-link wp-social-link-threads  wp-block-social-link"><a href="https://www.threads.com/@westonruter/post/DN3-3S-konm?xmt=AQF0yJplskrfY6oHVh4vRlE3DV_wKvinkt72VC0J-SlKYw" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M16.3 11.3c-.1 0-.2-.1-.2-.1-.1-2.6-1.5-4-3.9-4-1.4 0-2.6.6-3.3 1.7l1.3.9c.5-.8 1.4-1 2-1 .8 0 1.4.2 1.7.7.3.3.5.8.5 1.3-.7-.1-1.4-.2-2.2-.1-2.2.1-3.7 1.4-3.6 3.2 0 .9.5 1.7 1.3 2.2.7.4 1.5.6 2.4.6 1.2-.1 2.1-.5 2.7-1.3.5-.6.8-1.4.9-2.4.6.3 1 .8 1.2 1.3.4.9.4 2.4-.8 3.6-1.1 1.1-2.3 1.5-4.3 1.5-2.1 0-3.8-.7-4.8-2S5.7 14.3 5.7 12c0-2.3.5-4.1 1.5-5.4 1.1-1.3 2.7-2 4.8-2 2.2 0 3.8.7 4.9 2 .5.7.9 1.5 1.2 2.5l1.5-.4c-.3-1.2-.8-2.2-1.5-3.1-1.3-1.7-3.3-2.6-6-2.6-2.6 0-4.7.9-6 2.6C4.9 7.2 4.3 9.3 4.3 12s.6 4.8 1.9 6.4c1.4 1.7 3.4 2.6 6 2.6 2.3 0 4-.6 5.3-2 1.8-1.8 1.7-4 1.1-5.4-.4-.9-1.2-1.7-2.3-2.3zm-4 3.8c-1 .1-2-.4-2-1.3 0-.7.5-1.5 2.1-1.6h.5c.6 0 1.1.1 1.6.2-.2 2.3-1.3 2.7-2.2 2.7z"/></svg><span class="wp-block-social-link-label screen-reader-text">Threads</span></a></li></ul>



<p></p>
<p>The post <a href="https://weston.ruter.net/2025/08/27/the-site-speed-frontier-with-performance-lab-and-beyond/">The Site Speed Frontier with Performance Lab and Beyond</a> appeared first on <a href="https://weston.ruter.net">Weston Ruter</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://weston.ruter.net/2025/08/27/the-site-speed-frontier-with-performance-lab-and-beyond/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		<enclosure url="https://weston.ruter.net/wp-content/uploads/2025/08/speculative-loading-1-none-8s.mp4" length="128214" type="video/mp4" />
<enclosure url="https://weston.ruter.net/wp-content/uploads/2025/08/speculative-loading-2-conservative-prefetch-8s.mp4" length="123748" type="video/mp4" />
<enclosure url="https://weston.ruter.net/wp-content/uploads/2025/08/speculative-loading-3-moderate-prefetch-8s.mp4" length="183307" type="video/mp4" />
<enclosure url="https://weston.ruter.net/wp-content/uploads/2025/08/speculative-loading-4-moderate-prerender-8s.mp4" length="192639" type="video/mp4" />
<enclosure url="https://weston.ruter.net/wp-content/uploads/2025/08/view-transitions-disabled-with-navigation-via-links.mp4" length="435869" type="video/mp4" />
<enclosure url="https://weston.ruter.net/wp-content/uploads/2025/08/view-transitions-enabled-with-navigation-via-links.mp4" length="1024744" type="video/mp4" />
<enclosure url="https://weston.ruter.net/wp-content/uploads/2025/08/view-transitions-disabled-with-navigation-via-bfcache.mp4" length="513665" type="video/mp4" />
<enclosure url="https://weston.ruter.net/wp-content/uploads/2025/08/view-transitions-enabled-with-navigation-via-bfcache.mp4" length="947269" type="video/mp4" />
<enclosure url="https://weston.ruter.net/wp-content/uploads/2025/08/buddypress-sans-bfcache.mp4" length="537199" type="video/mp4" />
<enclosure url="https://weston.ruter.net/wp-content/uploads/2025/08/buddypress-with-bfcache.mp4" length="413941" type="video/mp4" />

		<post-id xmlns="com-wordpress:feed-additions:1">35813</post-id>	</item>
		<item>
		<title>Web Performance Milestone</title>
		<link>https://weston.ruter.net/2025/08/21/web-performance-milestone/</link>
					<comments>https://weston.ruter.net/2025/08/21/web-performance-milestone/#comments</comments>
		
		<dc:creator><![CDATA[Weston Ruter]]></dc:creator>
		<pubDate>Fri, 22 Aug 2025 06:13:20 +0000</pubDate>
				<category><![CDATA[WordPress]]></category>
		<category><![CDATA[performance]]></category>
		<guid isPermaLink="false">https://weston.ruter.net/?p=35922</guid>

					<description><![CDATA[<p>A couple months ago, this blog reached a web performance milestone which I shared on LinkedIn, Bluesky, Mastodon, Twitter: For the first time ever, I&#8217;ve just seen my blog appear in field metrics from CrUX (Chrome UX Report), albeit in desktop only and for the origin not an individual URL. Baby steps. In any case, [&#8230;]</p>
<p>The post <a href="https://weston.ruter.net/2025/08/21/web-performance-milestone/">Web Performance Milestone</a> appeared first on <a href="https://weston.ruter.net">Weston Ruter</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>A couple months ago, <a href="https://weston.ruter.net/">this blog</a> reached a web performance milestone which I shared on <a href="https://www.linkedin.com/posts/westonruter_for-the-first-time-ever-ive-just-seen-my-activity-7334960747849887744-ubB9">LinkedIn</a>, <a href="https://bsky.app/profile/weston.ruter.net/post/3lqkkjkaqc22q">Bluesky</a>, <a href="https://mastodon.social/@westonruter/114608757425002787">Mastodon</a>, <a href="https://x.com/westonruter/status/1929195196710875311">Twitter</a>:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>For the first time ever, I&#8217;ve just seen my blog appear in field metrics from CrUX (Chrome UX Report), albeit in desktop only and for the origin not an individual URL. Baby steps. In any case, Core Web Vitals Assessment: Passed <img src="https://weston.ruter.net/wp-content/plugins/local-twemoji/images/emoji/72x72/2705.png?ver=17-0-2-2" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
</blockquote>



<p>Yesterday, I checked <a href="https://pagespeed.web.dev/">PageSpeed Insights</a> again, and I was excited to discover that my blog is now also appearing in field metrics for mobile as well! And like desktop, the CWV assessment is also passing for mobile. The <a href="https://developer.chrome.com/docs/crux#:~:text=primarily%20that%20they%20must%20be%20publicly%20discoverable%20and%20there%20must%20be%20a%20large%20enough%20number%20of%20visitors%20in%20order%20to%20create%20a%20statistically%20significant%20dataset">eligibility criteria</a> for the CrUX dataset includes that “there must be a large enough number of visitors in order to create a statistically significant dataset.” Granted, my site is still only getting enough traffic for origin-level metrics, and I can&#8217;t see field metrics for the homepage URL specifically, but it&#8217;s another baby step! (Or maybe a toddler step?)</p>



<p>I&#8217;ve really been trying to double down this summer on tuning every bit of performance possible out of WordPress (on the frontend), using my site as a case study, and I&#8217;ve been sharing my findings in posts here. I hope the site traffic is an indication that the community has found my posts helpful. The learnings are also making their way into <a href="https://wordpress.org/plugins/performance-lab/">Performance Lab</a> feature plugins as well as in performance improvements on the <a href="https://make.wordpress.org/core/2025/07/28/roadmap-to-6-9/#performance-improvements">roadmap for WordPress 6.9</a>. I have some more posts that I&#8217;m working on. You can <a href="https://weston.ruter.net/subscribe/">subscribe</a> to get them in your inbox.</p>



<p>On August 27th (next Wednesday) at <a href="https://us.wordcamp.org/2025/">WordCamp US 2025</a> here in Portland, Oregon, I&#8217;m giving a talk called “<a href="https://us.wordcamp.org/2025/session/the-site-speed-frontier-with-performance-lab-and-beyond/"><strong>The Site Speed Frontier with Performance Lab and Beyond</strong></a>” at <time datetime="2025-08-21T11:30:00-07:00">11:30am PDT</time>. I hope to see you there, but it will also be livestreamed and recorded. I&#8217;ll be blogging an elaborated version of what I have time to share in my talk. (By the way, if you are attending in person, check out <a href="https://weston.ruter.net/2024/09/12/my-portland-picks/">My Portland Picks</a> post for what I recommend visitors check out!)</p>



<p>One takeaway I&#8217;ll be emphasizing in my talk is that we needn&#8217;t settle with sites merely <em>passing</em> the Core Web Vitals assessment or achieving a “perfect” 100 performance score in Lighthouse. Why be content with a good 2-second <abbr title="Largest Contentful Paint">LCP</abbr> when it could be half that or even practically zero? Web performance is a journey, and there&#8217;s always room for improvement. I can see from my blog&#8217;s field metrics, for example, that the <abbr title="Time To First Byte">TTFB</abbr> is hovering around the threshold between “needs improvement” and “poor”. In spite of this, the frontend is so tuned that on mobile the <abbr title="LCP minus/discounting the TTFB">LCP-TTFB</abbr> in CrUX is  400ms and on desktop it&#8217;s only 100ms.</p>



<p>I personally love optimizing the performance of WordPress sites, but I get it that this isn&#8217;t for everyone (nor should it be). By landing our improvements from the <a href="https://make.wordpress.org/performance/handbook/about-the-team/">Core Performance Team</a>, my hope is that WordPress core (and the ecosystem) will have best practices implemented by default so that site owners needn&#8217;t worry about performance.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>I had to take some <abbr title="PageSpeed Insights">PSI</abbr> screenshots to memorialize the milestone:</p>



<h2 class="wp-block-heading has-text-align-center">Field Data via CrUX</h2>



<div class="wp-block-columns alignwide is-not-stacked-on-mobile is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bce0341f8&quot;}" data-wp-interactive="core/image" data-wp-key="6975bce0341f8" class="wp-block-image aligncenter size-full wp-lightbox-container"><img data-recalc-dims="1" data-dominant-color="f9f9f9" data-has-transparency="false" style="--dominant-color: #f9f9f9;" loading="lazy" decoding="async" width="1920" height="1710" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 322px) 100vw, 322px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/mobile-crux-expanded.png?resize=1920%2C1710&#038;ssl=1" alt="Core Web Vitals Assessment for Mobile: Passed. LCP is 2.2s, CLS is 0, FCP is 2.1s, and TTFB 1.8s." class="wp-image-35933 not-transparent" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/mobile-crux-expanded.png?w=1920&amp;ssl=1 1920w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/mobile-crux-expanded.png?resize=300%2C267&amp;ssl=1 300w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/mobile-crux-expanded.png?resize=700%2C623&amp;ssl=1 700w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/mobile-crux-expanded.png?resize=768%2C684&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/mobile-crux-expanded.png?resize=1536%2C1368&amp;ssl=1 1536w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/mobile-crux-expanded.png?resize=150%2C134&amp;ssl=1 150w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button><figcaption class="wp-element-caption">Mobile</figcaption></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bce03498b&quot;}" data-wp-interactive="core/image" data-wp-key="6975bce03498b" class="wp-block-image aligncenter size-full wp-lightbox-container"><img data-recalc-dims="1" data-dominant-color="f8f9f9" data-has-transparency="false" style="--dominant-color: #f8f9f9;" loading="lazy" decoding="async" width="1920" height="1710" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 322px) 100vw, 322px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/desktop-crux-expanded.png?resize=1920%2C1710&#038;ssl=1" alt="Core Web Vitals Assessment for Desktop: Passed. LCP is 1.9s, CLS is 0, FCP is 1.9s, and TTFB 1.8s." class="wp-image-35934 not-transparent" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/desktop-crux-expanded.png?w=1920&amp;ssl=1 1920w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/desktop-crux-expanded.png?resize=300%2C267&amp;ssl=1 300w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/desktop-crux-expanded.png?resize=700%2C623&amp;ssl=1 700w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/desktop-crux-expanded.png?resize=768%2C684&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/desktop-crux-expanded.png?resize=1536%2C1368&amp;ssl=1 1536w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/desktop-crux-expanded.png?resize=150%2C134&amp;ssl=1 150w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button><figcaption class="wp-element-caption">Desktop</figcaption></figure>
</div>
</div>



<h2 class="wp-block-heading has-text-align-center">Lab Data via Lighthouse</h2>



<div class="wp-block-columns alignwide is-not-stacked-on-mobile is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bce0353a2&quot;}" data-wp-interactive="core/image" data-wp-key="6975bce0353a2" class="wp-block-image aligncenter size-full wp-lightbox-container"><img data-recalc-dims="1" data-dominant-color="f3f5f5" data-has-transparency="false" loading="lazy" decoding="async" width="1920" height="2316" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 322px) 100vw, 322px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/mobile-lighthouse.png?resize=1920%2C2316&#038;ssl=1" alt="Lighthouse performance score 100 for mobile, as well as 100 for the Accessibility, Best Practices, and SEO." class="wp-image-35926 not-transparent" style="--dominant-color: #f3f5f5; object-fit:cover" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/mobile-lighthouse.png?w=1920&amp;ssl=1 1920w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/mobile-lighthouse.png?resize=249%2C300&amp;ssl=1 249w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/mobile-lighthouse.png?resize=580%2C700&amp;ssl=1 580w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/mobile-lighthouse.png?resize=768%2C926&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/mobile-lighthouse.png?resize=1273%2C1536&amp;ssl=1 1273w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/mobile-lighthouse.png?resize=1698%2C2048&amp;ssl=1 1698w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/mobile-lighthouse.png?resize=150%2C181&amp;ssl=1 150w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button><figcaption class="wp-element-caption">Mobile</figcaption></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bce035b28&quot;}" data-wp-interactive="core/image" data-wp-key="6975bce035b28" class="wp-block-image aligncenter size-full wp-lightbox-container"><img data-recalc-dims="1" data-dominant-color="f1f4f3" data-has-transparency="false" loading="lazy" decoding="async" width="1920" height="2136" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 322px) 100vw, 322px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/desktop-lighthouse.png?resize=1920%2C2136&#038;ssl=1" alt="Lighthouse performance score 100 for desktop, as well as 100 for the Accessibility, Best Practices, and SEO." class="wp-image-35927 not-transparent" style="--dominant-color: #f1f4f3; object-fit:cover" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/desktop-lighthouse.png?w=1920&amp;ssl=1 1920w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/desktop-lighthouse.png?resize=270%2C300&amp;ssl=1 270w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/desktop-lighthouse.png?resize=629%2C700&amp;ssl=1 629w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/desktop-lighthouse.png?resize=768%2C854&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/desktop-lighthouse.png?resize=1381%2C1536&amp;ssl=1 1381w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/desktop-lighthouse.png?resize=1841%2C2048&amp;ssl=1 1841w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/08/desktop-lighthouse.png?resize=150%2C167&amp;ssl=1 150w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button><figcaption class="wp-element-caption">Desktop</figcaption></figure>
</div>
</div>



<p class="has-medium-font-size">Where I&#8217;ve shared this on social media if you want to discuss there:</p>



<ul class="wp-block-social-links is-layout-flex wp-block-social-links-is-layout-flex"><li class="wp-social-link wp-social-link-linkedin  wp-block-social-link"><a href="https://www.linkedin.com/posts/westonruter_web-performance-milestone-weston-ruter-activity-7364543117192032256-x7Wg" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M19.7,3H4.3C3.582,3,3,3.582,3,4.3v15.4C3,20.418,3.582,21,4.3,21h15.4c0.718,0,1.3-0.582,1.3-1.3V4.3 C21,3.582,20.418,3,19.7,3z M8.339,18.338H5.667v-8.59h2.672V18.338z M7.004,8.574c-0.857,0-1.549-0.694-1.549-1.548 c0-0.855,0.691-1.548,1.549-1.548c0.854,0,1.547,0.694,1.547,1.548C8.551,7.881,7.858,8.574,7.004,8.574z M18.339,18.338h-2.669 v-4.177c0-0.996-0.017-2.278-1.387-2.278c-1.389,0-1.601,1.086-1.601,2.206v4.249h-2.667v-8.59h2.559v1.174h0.037 c0.356-0.675,1.227-1.387,2.526-1.387c2.703,0,3.203,1.779,3.203,4.092V18.338z"></path></svg><span class="wp-block-social-link-label screen-reader-text">LinkedIn</span></a></li>

<li class="wp-social-link wp-social-link-bluesky  wp-block-social-link"><a href="https://bsky.app/profile/weston.ruter.net/post/3lqkkjkaqc22q" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M6.3,4.2c2.3,1.7,4.8,5.3,5.7,7.2.9-1.9,3.4-5.4,5.7-7.2,1.7-1.3,4.3-2.2,4.3.9s-.4,5.2-.6,5.9c-.7,2.6-3.3,3.2-5.6,2.8,4,.7,5.1,3,2.9,5.3-5,5.2-6.7-2.8-6.7-2.8,0,0-1.7,8-6.7,2.8-2.2-2.3-1.2-4.6,2.9-5.3-2.3.4-4.9-.3-5.6-2.8-.2-.7-.6-5.3-.6-5.9,0-3.1,2.7-2.1,4.3-.9h0Z"></path></svg><span class="wp-block-social-link-label screen-reader-text">Bluesky</span></a></li>

<li class="wp-social-link wp-social-link-twitter  wp-block-social-link"><a href="https://x.com/westonruter/status/1958777784282563018" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M22.23,5.924c-0.736,0.326-1.527,0.547-2.357,0.646c0.847-0.508,1.498-1.312,1.804-2.27 c-0.793,0.47-1.671,0.812-2.606,0.996C18.324,4.498,17.257,4,16.077,4c-2.266,0-4.103,1.837-4.103,4.103 c0,0.322,0.036,0.635,0.106,0.935C8.67,8.867,5.647,7.234,3.623,4.751C3.27,5.357,3.067,6.062,3.067,6.814 c0,1.424,0.724,2.679,1.825,3.415c-0.673-0.021-1.305-0.206-1.859-0.513c0,0.017,0,0.034,0,0.052c0,1.988,1.414,3.647,3.292,4.023 c-0.344,0.094-0.707,0.144-1.081,0.144c-0.264,0-0.521-0.026-0.772-0.074c0.522,1.63,2.038,2.816,3.833,2.85 c-1.404,1.1-3.174,1.756-5.096,1.756c-0.331,0-0.658-0.019-0.979-0.057c1.816,1.164,3.973,1.843,6.29,1.843 c7.547,0,11.675-6.252,11.675-11.675c0-0.178-0.004-0.355-0.012-0.531C20.985,7.47,21.68,6.747,22.23,5.924z"></path></svg><span class="wp-block-social-link-label screen-reader-text">Twitter</span></a></li>

<li class="wp-social-link wp-social-link-threads  wp-block-social-link"><a href="https://www.threads.com/@westonruter/post/DNpbMfgOwVe?xmt=AQF06IGjZm5nYqgg4N9C_eky4bgCiMNG7ZZVDcJhI9_RKw" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M16.3 11.3c-.1 0-.2-.1-.2-.1-.1-2.6-1.5-4-3.9-4-1.4 0-2.6.6-3.3 1.7l1.3.9c.5-.8 1.4-1 2-1 .8 0 1.4.2 1.7.7.3.3.5.8.5 1.3-.7-.1-1.4-.2-2.2-.1-2.2.1-3.7 1.4-3.6 3.2 0 .9.5 1.7 1.3 2.2.7.4 1.5.6 2.4.6 1.2-.1 2.1-.5 2.7-1.3.5-.6.8-1.4.9-2.4.6.3 1 .8 1.2 1.3.4.9.4 2.4-.8 3.6-1.1 1.1-2.3 1.5-4.3 1.5-2.1 0-3.8-.7-4.8-2S5.7 14.3 5.7 12c0-2.3.5-4.1 1.5-5.4 1.1-1.3 2.7-2 4.8-2 2.2 0 3.8.7 4.9 2 .5.7.9 1.5 1.2 2.5l1.5-.4c-.3-1.2-.8-2.2-1.5-3.1-1.3-1.7-3.3-2.6-6-2.6-2.6 0-4.7.9-6 2.6C4.9 7.2 4.3 9.3 4.3 12s.6 4.8 1.9 6.4c1.4 1.7 3.4 2.6 6 2.6 2.3 0 4-.6 5.3-2 1.8-1.8 1.7-4 1.1-5.4-.4-.9-1.2-1.7-2.3-2.3zm-4 3.8c-1 .1-2-.4-2-1.3 0-.7.5-1.5 2.1-1.6h.5c.6 0 1.1.1 1.6.2-.2 2.3-1.3 2.7-2.2 2.7z"/></svg><span class="wp-block-social-link-label screen-reader-text">Threads</span></a></li>

<li class="wp-social-link wp-social-link-mastodon  wp-block-social-link"><a href="https://mastodon.social/@westonruter/115070980072198942" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M23.193 7.879c0-5.206-3.411-6.732-3.411-6.732C18.062.357 15.108.025 12.041 0h-.076c-3.068.025-6.02.357-7.74 1.147 0 0-3.411 1.526-3.411 6.732 0 1.192-.023 2.618.015 4.129.124 5.092.934 10.109 5.641 11.355 2.17.574 4.034.695 5.535.612 2.722-.15 4.25-.972 4.25-.972l-.09-1.975s-1.945.613-4.129.539c-2.165-.074-4.449-.233-4.799-2.891a5.499 5.499 0 0 1-.048-.745s2.125.52 4.817.643c1.646.075 3.19-.097 4.758-.283 3.007-.359 5.625-2.212 5.954-3.905.517-2.665.475-6.507.475-6.507zm-4.024 6.709h-2.497V8.469c0-1.29-.543-1.944-1.628-1.944-1.2 0-1.802.776-1.802 2.312v3.349h-2.483v-3.35c0-1.536-.602-2.312-1.802-2.312-1.085 0-1.628.655-1.628 1.944v6.119H4.832V8.284c0-1.289.328-2.313.987-3.07.68-.758 1.569-1.146 2.674-1.146 1.278 0 2.246.491 2.886 1.474L12 6.585l.622-1.043c.64-.983 1.608-1.474 2.886-1.474 1.104 0 1.994.388 2.674 1.146.658.757.986 1.781.986 3.07v6.304z"/></svg><span class="wp-block-social-link-label screen-reader-text">Mastodon</span></a></li></ul>



<p></p>
<p>The post <a href="https://weston.ruter.net/2025/08/21/web-performance-milestone/">Web Performance Milestone</a> appeared first on <a href="https://weston.ruter.net">Weston Ruter</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://weston.ruter.net/2025/08/21/web-performance-milestone/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">35922</post-id>	</item>
		<item>
		<title>Instant Back/Forward Navigations in WordPress</title>
		<link>https://weston.ruter.net/2025/07/23/instant-back-forward-navigations-in-wordpress/</link>
					<comments>https://weston.ruter.net/2025/07/23/instant-back-forward-navigations-in-wordpress/#respond</comments>
		
		<dc:creator><![CDATA[Weston Ruter]]></dc:creator>
		<pubDate>Wed, 23 Jul 2025 07:51:21 +0000</pubDate>
				<category><![CDATA[WordPress]]></category>
		<category><![CDATA[bfcache]]></category>
		<category><![CDATA[performance]]></category>
		<category><![CDATA[speculative-loading]]></category>
		<guid isPermaLink="false">https://weston.ruter.net/?p=35588</guid>

					<description><![CDATA[<p>The new Instant Back/Forward plugin enables instant navigations in the browser history via the bfcache, particularly while logged in.</p>
<p>The post <a href="https://weston.ruter.net/2025/07/23/instant-back-forward-navigations-in-wordpress/">Instant Back/Forward Navigations in WordPress</a> appeared first on <a href="https://weston.ruter.net">Weston Ruter</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="is-style-text-subtitle is-style-text-subtitle--2">The new <a href="https://wordpress.org/plugins/nocache-bfcache/"><strong>Instant Back/Forward</strong></a> plugin (née “No-cache BFCache”) enables instant back/forward navigations, particularly while logged in. See <a href="#demos">demos below</a> for the impact this has on browsing.</p>



<p>The speed of page navigations in WordPress has seen a big boost in 6.8 with the introduction of <a href="https://make.wordpress.org/core/2025/03/06/speculative-loading-in-6-8/">Speculative Loading</a>. When paired with the original <a href="https://wordpress.org/plugins/speculation-rules/">feature plugin</a>, site visitors can experience instant page navigations thanks to its opt-ins for prerendering and “moderate eagerness”. However, WordPress does not enable such instant navigations by default. WordPress core must take a conservative approach since prerendering may cause compatibility problems, like with analytics or ads. There is also a sustainability concern with moderate prerendering since a user may momentarily hover over a link but not intend to follow it at all; this results in an unused prerender, which can waste a user&#8217;s bandwidth (and CPU to a limited extent) as well as increase the load on the web server (which can be problematic on shared hosts without page caching). Furthermore, even when prerendering is enabled, not all visitors may be able to experience instant page loads because:&nbsp;</p>



<ol class="wp-block-list">
<li>Moderate eagerness is limited on mobile devices (where there is no hover heuristic on touch screens).</li>



<li>Speculative Loading is not enabled when a user is logged in. <mark style="background-color:#FFEE58" class="has-inline-color">(<strong>Updates:</strong> See <a href="https://github.com/WordPress/performance/pull/2097">PR</a> which introduces a frontend opt-in, and the <a href="https://github.com/westonruter/speculative-loading-admin">Speculative Loading Admin</a> plugin for a backend opt-in.)</mark></li>



<li>The Speculation Rules API is currently only <a href="https://caniuse.com/mdn-html_elements_script_type_speculationrules">supported</a> in Chromium, leaving Safari and Firefox users out.</li>
</ol>



<p>Nevertheless, there is a much older web platform technology that enables “prerendering” <em>and</em> which is supported in all browsers: the <a href="https://developer.mozilla.org/en-US/docs/Glossary/bfcache">back/forward cache</a> (bfcache). This instant page navigation involves no network traffic and no CPU load. Previously visited pages are stored in memory as a snapshot with their entire state so that they can be restored instantly. Navigating to pages stored in bfcache is as fast as switching open browser tabs.</p>



<p>Back/forward history navigations are very common, as according to the <a href="https://web.dev/articles/bfcache">web.dev article</a> on bfcache:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Chrome usage data shows that 1 in 10 navigations on desktop and 1 in 5 on mobile are either back or forward. With bfcache enabled, browsers could eliminate the data transfer and time spent loading for billions of web pages every single day!</p>
</blockquote>



<p>Also learn more via the <a href="https://youtu.be/Y2IVv5KnrmI">following video</a>:</p>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" title="Debugging bfcache, make your page load instantly #DevToolsTips" width="500" height="281" src="https://www.youtube.com/embed/Y2IVv5KnrmI?feature=oembed&#038;rel=0&#038;modestbranding=1" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>



<p>While Speculative Loading enables prerendering, bfcache involves “pre<em>vious</em>-rendering”.</p>



<p>The good news is that 84% of WordPress origins in <a href="https://httparchive.org/">HTTP Archive</a> are already bfcache-eligible (based on <a href="https://gist.github.com/gilbertococchi/083c1516149cf5f70b103451d1790cbc">this query</a> from <a href="https://github.com/gilbertococchi">Gilberto Cocchi</a>). <strong>However, the most frequent user of your site may still not be able to experience instant page navigations via bfcache: <em>you!</em></strong> While browsing around the WP Admin and navigating the frontend of your site, the pages most likely aren&#8217;t eligible for bfcache due to various <a href="https://developer.mozilla.org/en-US/docs/Web/API/Performance_API/Monitoring_bfcache_blocking_reasons#blocking_reasons">blocking reasons</a>. This sluggish experience extends not only to administrators but also to users of sites which require authentication, including e-commerce (e.g. WooCommerce), social (e.g. BuddyPress), and any membership sites. The 84% metric above doesn&#8217;t take into account these types of WordPress sites since HTTP Archive exclusively crawls public URLs without authentication. Users browsing WordPress sites on shared hosts or when on a slow connection will be especially frustrated by slow back/forward navigations. Enabling instant back/forward navigations will not only make your users happier (especially you), but it can also improve your <abbr title="Core Web Vitals">CWV</abbr> passing rate <a href="https://developer.chrome.com/blog/crux-navigation-types">in <abbr title="Chrome User Experience Report">CrUX</abbr></a>.</p>



<p>I&#8217;ve been on a mission to bring back bfcache (or rather to bring it forward).</p>



<h2 class="wp-block-heading" id="stale-content-in-page-caches">Stale Content in Page Caches</h2>



<p>Before going further, I wanted to call out a potentially negative consequence of navigating cached pages. While instant back/forward navigations can provide a big performance boost to the user experience, they can also be a source of confusion when navigating to a cached page with stale content. </p>



<p>Consider the case of an e-commerce site, where a user is on the shopping cart page. From there, they click a link to a related product, and from that product page they add it to the cart dynamically (e.g. via Ajax). At this point, if they hit the back button to return to the cart page and the cart is restored from a page cache, they may not see the newly added product in the cart. A user can simply reload the page to fix this, but this can also be handled automatically. </p>



<p>As described in the “<a href="https://web.dev/articles/bfcache#update-data-after-restore">Update stale or sensitive data after bfcache restore</a>” section of the web.dev post, the <code>pageshow</code> event with the <code>persisted</code> property (further <a href="#jit-page-cache-invalidation">discussed</a> below) indicates when a page was restored from bfcache. This event can be used to update the DOM with the latest shopping cart details. In fact, WooCommerce already <a href="https://github.com/woocommerce/woocommerce/blob/47845a498f0918a90d60ab3adca204d13a712cde/plugins/woocommerce/client/blocks/assets/js/base/context/hooks/cart/use-store-cart-event-listeners.ts#L40-L50">implements this</a> for its cart:</p>


<pre class="wp-block-code"><span><code class="hljs language-typescript"><span class="hljs-keyword">const</span> refreshCachedCartData = ( event: PageTransitionEvent ): <span class="hljs-function"><span class="hljs-params">void</span> =&gt;</span> {
	<span class="hljs-keyword">if</span> ( event?.persisted || getNavigationType() === <span class="hljs-string">'back_forward'</span> ) {
		dispatch( cartStore ).invalidateResolutionForStore();
	}
};
<span class="hljs-comment">/* ... */</span>
<span class="hljs-built_in">window</span>.addEventListener( <span class="hljs-string">'pageshow'</span>, refreshCachedCartData );</code></span></pre>


<p>Simply searching a plugin&#8217;s codebase for “pageshow” (e.g. <a href="https://github.com/search?q=repo%3Awoocommerce%2Fwoocommerce%20pageshow&amp;type=code">in Woo</a>) should indicate whether bfcache navigations are accounted for. </p>



<p>Note also that stale content is also an issue <a href="https://github.com/WordPress/performance/issues/1774">we&#8217;ve faced</a> with Speculative Loading, especially when using non-conservative eagerness.</p>



<h2 class="wp-block-heading">Breaking Caching to Preserve Privacy</h2>



<p>My bfcache work began two years ago when I started collaborating on <a href="https://core.trac.wordpress.org/ticket/55491">#55491</a> to eliminate the use of the <a href="https://developer.chrome.com/docs/web-platform/deprecating-unload">deprecated</a> <code>unload</code> event handler in WordPress core. Not only does this event fire unreliably, but it also has the side effect of making a page <a href="https://web.dev/articles/bfcache#never-use-the-unload-event">ineligible for bfcache</a> via the “<a href="https://developer.mozilla.org/en-US/docs/Web/API/Performance_API/Monitoring_bfcache_blocking_reasons#unload-listener">unload-listener</a>” blocking reason. I had <a href="https://github.com/GoogleChromeLabs/wpp-research/pull/75">queried</a> HTTP Archive myself at the time, and I found it to be the second most common blocking reason. So in <a href="https://core.trac.wordpress.org/changeset/56809">r56809</a> we eliminated all uses of <code>unload</code>, including from:</p>



<ul class="wp-block-list">
<li>Heartbeat API</li>



<li>Post locking in the classic editor</li>



<li>Post previews</li>
</ul>



<p>However, even with the use of <code>unload</code> eliminated, I was confused why I wasn&#8217;t finding pages to be eligible for bfcache in my testing. (The removal of <code>unload</code> in core likely didn&#8217;t make a dent in bfcache eligibility in HTTP Archive since only <a href="https://github.com/GoogleChromeLabs/wpp-research/pull/75#:~:text=Lastly%2C%20in%20heartbeat%2Dscript%2Dpresence.sql%20I%20check%20to%20see%20how%20common%20the%20Heartbeat%20script%20actually%20is%3A">0.25%</a> of unauthenticated pages had the Heartbeat API present.) It turns out that several weeks before I started working on this, a <a href="https://core.trac.wordpress.org/changeset/55968">commit</a> had landed which <em>intentionally</em> broke browser page caching (both bfcache and HTTP cache): the <code>no-store</code> directive was added to the <code>Cache-Control</code> response header for logged-in users. (Specifically, this was added to the <code><a href="https://developer.wordpress.org/reference/functions/wp_get_nocache_headers/">wp_get_nocache_headers()</a></code> function.) This introduced the “<a href="https://developer.mozilla.org/en-US/docs/Web/API/Performance_API/Monitoring_bfcache_blocking_reasons#response-cache-control-no-store">response-cache-control-no-store</a>” bfcache blocking reason, which was actually the <a href="https://github.com/GoogleChromeLabs/wpp-research/pull/75#:~:text=Pages%20whose%20main%20resource%20has%20cache%2Dcontrol%3Ano%2Dstore%20cannot%20enter%20back/forward%20cache.">most common reason for ineligibility</a>, just above <code>unload</code> in my HTTP Archive query. The reason for adding this is found in <a href="https://core.trac.wordpress.org/ticket/21938">#21938</a> which proposed adding the <code>no-store</code> directive in order to explicitly <em>disable</em> caching so that authenticated pages do not get stored in the browser cache. This was followed up with <a href="https://core.trac.wordpress.org/ticket/61942">#61942</a> to also send <code>no-store</code> on the frontend while a user is logged in. The rationale for this is rooted in privacy, per the commit message:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>The intention behind this change is to prevent sensitive data in responses for logged in users being cached and available to others, for example via the browser history after the user logs out.</p>
</blockquote>



<p>Consider an administrator who is logged in to WordPress on a shared computer and is working on something sensitive, like managing API keys on an admin screen. A malicious person could navigate back to that admin screen via the back button after the user had logged out. Nevertheless, the “bad guy” will need to act fast because a browser won&#8217;t store cached pages indefinitely; Chrome, for example, holds onto pages in bfcache for <a href="https://developer.chrome.com/docs/web-platform/bfcache-ccns#:~:text=The%20bfcache%20timeout%20for%20Cache%2DControl%3A%20no%2Dstore%20pages%20is%20also%20reduced%20to%203%20minutes%20(from%2010%20minutes%20used%20for%20pages%20which%20don%27t%20use%20Cache%2DControl%3A%20no%2Dstore)%20to%20further%20reduce%20risk.">10 minutes</a>. (Tip: When using a shared computer, always exit the browser after logging out of all sites when ending work.) So while <code>no-store</code> here does improve privacy, it does so at the expense of the user experience by degrading the performance of back/forward navigations. Even when pages aren&#8217;t eligible for bfcache, removing <code>no-store</code> still improves back/forward navigation performance since pages can be served from the browser&#8217;s HTTP cache (although the DOM has to be re-built and scripts have to be re-executed).</p>



<p><strong>Surely there must be a way to preserve privacy and promote performance together.</strong></p>



<p>It&#8217;s important to note that the <code>no-store</code> directive not only prevents pages from being cached by the browser, but it also preserves privacy by preventing proxies from caching pages, per <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#no-store">MDN</a>:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>The <code>no-store</code> response directive indicates that any caches of any kind (private or shared) should not store this response.&nbsp;&nbsp;</p>
</blockquote>



<p>Keeping authenticated pages out of page caches prevents embarrassing scenarios like serving a user the shopping cart page for another user. Nevertheless, there is a more tailored <code>Cache-Control</code> mechanism for this purpose: the <code>private</code> directive. Again, per <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#private">MDN</a>:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>The private response directive indicates that the response can be stored only in a private cache (e.g., local caches in browsers). ¶&nbsp;You should add the private directive for user-personalized content, especially for responses received after login and for sessions managed via cookies. ¶&nbsp;If you forget to add private to a response with personalized content, then that response can be stored in a shared cache and end up being reused for multiple users, which can cause personal information to leak.</p>
</blockquote>



<p>This is what was originally requested in <a href="https://core.trac.wordpress.org/ticket/57627">#57627</a>, but both <code>private</code> and <code>no-store</code> were added together, with the <a href="https://core.trac.wordpress.org/changeset/55968">commit</a> message reasoning:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>The <code>private</code> directive complements the <code>no-store</code> directive by specifying that the response contains private information that should not be stored in a public cache. Some proxy caches may ignore the <code>no-store</code> directive but respect the <code>private</code> directive, thus it is included.</p>
</blockquote>



<p>So how can the <code>private</code> directive be retained for proxies, while removing the <code>no-store</code> directive so that a user can benefit from browser caching but not at the expense of privacy?</p>



<h2 class="wp-block-heading">Preserving Privacy while Caching</h2>



<p>In order to omit the <code>no-store</code> directive and enable instant back/forward navigations while preserving privacy, a way is needed to evict pages from the browser cache when a user logs out. In my research, there are three mechanisms to do this, with various degrees of browser support.</p>



<h3 class="wp-block-heading">The <code>Clear-Site-Data</code> Header</h3>



<p>The most straightforward mechanism to invalidate pages from bfcache would at first seem to be the <code>Clear-Site-Data</code> HTTP response header. This is supposedly Baseline 2023 Newly Available, but there is an asterisk: “Some parts of this feature may have varying levels of support.” Per <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Clear-Site-Data">MDN</a>, this header “sends a signal to the client that it should remove all browsing data of certain types (cookies, storage, cache) associated with the requesting website.” In fact, the <code>cache</code> directive for this header seems to be exactly what is needed, as it even <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Clear-Site-Data#cache">explicitly calls</a> out bfcache:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>The server signals that the client should remove locally cached data (the browser cache, see <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Caching">HTTP caching</a>) for the origin of the response URL. Depending on the browser, this might also clear out things like pre-rendered pages, <a href="https://developer.mozilla.org/en-US/docs/Glossary/bfcache">backwards-forwards cache</a>, script caches, WebGL shader caches, or address bar suggestions.</p>
</blockquote>



<p>However, note the word “might”. In my testing, sending this header does currently evict pages from bfcache in Chrome. But, there is an <a href="https://github.com/w3c/webappsec-clear-site-data/issues/73">open spec question</a> for whether Clear-Site-Data should clear bfcache; it mentions <a href="https://github.com/w3c/webappsec-clear-site-data/issues/68">dropping</a> the <code>cache</code> directive entirely, and it suggests that a different <code>executionContexts</code> directive may be more appropriate (but its browser support is even worse and it may be <a href="https://github.com/w3c/webappsec-clear-site-data/issues/59">dropped</a> from the spec). Firefox <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1671182">dropped support</a> for the <code>cache</code> directive but then <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1930500">restored</a> it, although in testing it seems Firefox only evicts pages from the HTTP cache and not from bfcache. Finally, Safari doesn&#8217;t seem to evict pages from either the HTTP cache or bfcache when this header is sent.&nbsp;</p>



<p>When/if browser support for this is improved, implementing eviction of pages from browser caches could be as simple as:</p>


<pre class="wp-block-code"><span><code class="hljs language-php">add_action(
	<span class="hljs-string">'clear_auth_cookie'</span>,
	<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">()</span> </span>{
		header( <span class="hljs-string">'Clear-Site-Data: "cache"'</span> );
	}
);</code></span></pre>


<p>Ultimately, while this header seems to be tailor-made for evicting pages from browser caches, it cannot be relied on since it is clearly not widely available in Baseline, and not even “newly available” either. There is also a Chromium bug (<a href="https://issues.chromium.org/issues/40233601">40233601</a>) where sending this header can greatly delay page response times, possibly causing a logout link to take half a minute to respond. The final mark against <code>Clear-Site-Data</code> is that the header requires a secure context (HTTPS), so the WordPress sites still on HTTP could not evict pages from browser caches using this header (although privacy could be the last of their concerns at this point).</p>



<p>So to preserve privacy, another browser page cache invalidation method is currently needed.</p>



<h3 class="wp-block-heading">Broadcast Channel</h3>



<p>Going back to the aforementioned bfcache <a href="https://developer.mozilla.org/en-US/docs/Web/API/Performance_API/Monitoring_bfcache_blocking_reasons#blocking_reasons">blocking reasons</a>, one of them is “<a href="https://developer.mozilla.org/en-US/docs/Web/API/Performance_API/Monitoring_bfcache_blocking_reasons#broadcastchannel-message">broadcastchannel-message</a>”:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>While the page was stored in back/forward cache, a <a href="https://developer.mozilla.org/en-US/docs/Web/API/BroadcastChannel">BroadcastChannel</a> connection on the page received a message to trigger a <a href="https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent">message</a> event.</p>
</blockquote>



<p>I went down a rabbit hole with this one, thinking I could leverage this blocking reason as a bfcache eviction mechanism. In my discarded implementation, all authenticated pages include a script which listens to a broadcast channel such as “auth_change”. Then, to evict authenticated pages from bfcache upon a successful logout (or logging in as another user), a script can be included on the page which simply broadcasts an arbitrary message to this same “auth_change” broadcast channel. This causes the eviction of any authenticated pages in bfcache which are listening to messages from this broadcast channel.</p>



<p>I was seeing very promising results with this approach, where pages were being successfully evicted from bfcache in Chromium (Chrome/Edge) and Firefox. Nevertheless, eviction was not happening in Safari, which apparently has not yet implemented this blocking reason. <em>However</em>, this approach fell apart once I closed DevTools.</p>



<p>Normally when DevTools is open I have “Disable cache” checked in the Network panel. This disables the HTTP cache, but not the bfcache. What I would see then is if I navigated around the WP Admin and then logged out in a second tab, hitting the back button in the first tab would immediately take me to the login screen. This is exactly what I wanted. However, once I closed DevTools, I started seeing pages served from the browser&#8217;s HTTP cache when navigating back/forward. Because the <code>no-store</code> directive is removed, this means the browser may actually serve pages from HTTP cache when navigating back/forward, even when the <code>Cache-Control</code> header has <code>no-cache</code>, <code>must-revalidate</code>, and <code>max-age=0</code>. And here, when the user logged out, they shouldn&#8217;t be served from either HTTP cache or bfcache to preserve privacy. So this was not a viable approach in the end.</p>



<h3 class="wp-block-heading" id="jit-page-cache-invalidation">Just-in-time Page Cache Invalidation</h3>



<p>Ultimately, the most reliable solution I&#8217;ve found to invalidate pages from both bfcache and the HTTP cache is to do so just-in-time with JavaScript: When a page is restored from a browser cache and it is determined to be stale, the page contents are wiped and the page is reloaded. This approach is also referenced in the aforementioned “<a href="https://web.dev/articles/bfcache#minimize-no-store">Update stale or sensitive data after bfcache restore</a>” section on web.dev, although I&#8217;m accounting for pages not only restored from bfcache but also the HTTP cache. </p>



<p>My implementation involves the creation of a new session token when the user authenticates. This session token is served in the HTML with each authenticated page, and it is also set as a <code>wordpress_​bfcache_​session_​{COOKIEHASH}</code> cookie which can be read by JavaScript. When a user logs out, this session token cookie is cleared. Whenever a page is loaded, the session token in the HTML is compared with the current session token in the cookie. When they don&#8217;t match, then the page is invalidated. If a page is served from bfcache, then the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/pageshow_event"><code>pageshow</code> event</a> (with <code>persisted</code> true) is used to do this check; if a page is served from the HTTP cache, then the check is done when the script module is executed. If the user had logged out, then reloading the page will take them to the login screen which, after authenticating, will redirect them back to the page they had been on.</p>



<p>As this page cache invalidation mechanism depends on JavaScript, scripting must be enabled to be eligible to omit the <code>no-store</code> directive from the <code>Cache-Control</code> header. This JavaScript detection can be done when the user submits the login form.</p>



<p>This implementation has been made available in a new plugin: <strong>Instant Back/Forward</strong>. It is available <a href="https://wordpress.org/plugins/nocache-bfcache/">on WordPress.org</a> and <a href="https://github.com/westonruter/nocache-bfcache/">on GitHub</a>. This is a feature plugin to implement <a href="https://core.trac.wordpress.org/ticket/63636">#63636</a> in WordPress core.</p>



<figure class="wp-block-embed is-type-wp-embed is-provider-plugin-directory wp-block-embed-plugin-directory"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="GYfwAliKQL"><a href="https://wordpress.org/plugins/nocache-bfcache/">Instant Back/Forward</a></blockquote><iframe loading="lazy" class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  title="&#8220;Instant Back/Forward&#8221; &#8212; Plugin Directory" src="https://wordpress.org/plugins/nocache-bfcache/embed/#?secret=tpMhThLk7N#?secret=GYfwAliKQL" data-secret="GYfwAliKQL" width="500" height="282" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>



<h2 class="wp-block-heading" id="demos">Demos</h2>



<p>In the following demos, I have Slow 4G network emulation enabled along with CPU throttling. This is in order to better simulate what an average user may experience when navigating, such as on a mid-range device potentially with a WordPress site on a shared host.</p>



<h3 class="wp-block-heading">Demo: Navigating the WordPress Admin</h3>



<p>After navigating from the Dashboard to the posts list table and then opening a post to edit, you can see the difference in speed navigating back and forth through the browser history:</p>



<div class="wp-block-columns alignwide is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<h4 class="wp-block-heading has-text-align-center">Without bfcache</h4>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-4-3 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" title="Navigating the WordPress Admin without Back/forward Cache (bfcache)" width="500" height="375" src="https://www.youtube.com/embed/Cz-_L6q9ZRc?start=22&#038;feature=oembed&#038;rel=0&#038;modestbranding=1" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<h4 class="wp-block-heading has-text-align-center">With bfcache</h4>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-4-3 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" title="Navigating the WordPress Admin with Back/forward Cache (bfcache)" width="500" height="375" src="https://www.youtube.com/embed/z4dILiwe0Rk?start=27&#038;feature=oembed&#038;rel=0&#038;modestbranding=1" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>
</div>
</div>



<h3 class="wp-block-heading">Demo: Navigating the WordPress Frontend</h3>



<p>Here you can see me draft a message in a BuddyPress activity update. Then I navigate to another URL, and then I go back and forward:</p>



<div class="wp-block-columns alignwide is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<h4 class="wp-block-heading has-text-align-center">Without bfcache</h4>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-4-3 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" title="Navigating the WordPress Frontend without Back/forward Cache (bfcache)" width="500" height="375" src="https://www.youtube.com/embed/Ko1w_SRlCig?feature=oembed&#038;rel=0&#038;modestbranding=1" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div><figcaption class="wp-element-caption">The drafted BuddyPress activity update is lost when navigating away from the page before submitting. The activity feed and Tweet have to be reconstructed with each back/forward navigation.</figcaption></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<h4 class="wp-block-heading has-text-align-center">With bfcache</h4>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-4-3 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" title="Navigating the WordPress Frontend with Back/forward Cache (bfcache)" width="500" height="375" src="https://www.youtube.com/embed/bzGm6LcHHAs?feature=oembed&#038;rel=0&#038;modestbranding=1" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div><figcaption class="wp-element-caption">The drafted BuddyPress activity update is preserved when navigating away from the page without submitting. The activity feed and Tweet do not have to be reconstructed when navigating to previously visited pages via the back/forward buttons.</figcaption></figure>
</div>
</div>



<h2 class="wp-block-heading">Appendix</h2>



<p>WooCommerce has been serving pages with <code>no-store</code> even for unauthenticated users on its Cart, Checkout, and Account pages. This slowed down navigating a store, even though Woo already implements support for ensuring a cart is up-to-date via the <code>pageshow</code> event, as <a href="#stale-content-in-page-caches">mentioned</a> above. The lack of bfcache means not only that navigating back to the cart is much slower than it needs to be, but there can also be data loss, for example an entered coupon but accidentally not applied. In PR <a href="https://github.com/woocommerce/woocommerce/pull/58445">#58445</a> the sending of <code>no-store</code> was removed. This is slated to be part of v10.1. See <a href="https://github.com/woocommerce/woocommerce/pull/58445#issuecomment-3014404754">before/after demo videos</a>.</p>



<p>Similarly, in Jetpack certain admin screens are sending <code>no-store</code> which slow down back/forward navigations. In Jetpack 14.9, PR <a href="https://github.com/Automattic/jetpack/pull/44322">#44322</a> removes this and greatly speeds up navigating to/from Jetpack admin screens, as seen in the <a href="https://github.com/Automattic/jetpack/pull/44322#:~:text=no%2Dstore.-,Demo%20Video,-Compare%20navigating%20back">demo videos</a>.</p>



<p>Sometimes the instantaneous navigations enabled by bfcache (and prerendering in Speculative Loading) can be a bit jarring, since pages load faster than expected. So the Instant Back/Forward plugin pairs well with the <a href="https://wordpress.org/plugins/view-transitions/">View Transitions</a> plugin which now features an Admin View Transitions opt-in. This helps smooth the overall experience.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>Special thanks to <a href="https://kinsta.com/">Kinsta</a> for <a href="https://github.com/sponsors/westonruter">sponsoring</a> part of my time contributing to WordPress while I&#8217;ve been <a href="https://weston.ruter.net/2025/05/14/a-decade-as-a-core-committer-my-wordpress-contribution-history/">between full time roles</a>. I also <a href="https://www.youtube.com/watch?v=d_I_JsFi2M0&amp;t=842s">discussed</a> this bfcache project in my “<a href="https://youtu.be/d_I_JsFi2M0">From Cassette Tapes to Core Commits: Weston Ruter’s WordPress Journey</a>” interview with <a href="https://www.linkedin.com/in/rogerwilliamsmedia?lipi=urn%3Ali%3Apage%3Ad_flagship3_profile_view_base_contact_details%3BSVYGJ1iQReyyHVN4aXnkag%3D%3D">Roger Williams</a> last week.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="has-medium-font-size">Where I&#8217;ve shared this, if you want to boost:</p>



<ul class="wp-block-social-links is-layout-flex wp-block-social-links-is-layout-flex"><li class="wp-social-link wp-social-link-linkedin  wp-block-social-link"><a href="https://www.linkedin.com/posts/westonruter_instant-backforward-navigations-in-wordpress-activity-7353696655562452995-zu-E?utm_source=share&#038;utm_medium=member_desktop&#038;rcm=ACoAAACIeJ0BUsdEu-G5aiGg1JkXrMQ-C6tbCsI" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M19.7,3H4.3C3.582,3,3,3.582,3,4.3v15.4C3,20.418,3.582,21,4.3,21h15.4c0.718,0,1.3-0.582,1.3-1.3V4.3 C21,3.582,20.418,3,19.7,3z M8.339,18.338H5.667v-8.59h2.672V18.338z M7.004,8.574c-0.857,0-1.549-0.694-1.549-1.548 c0-0.855,0.691-1.548,1.549-1.548c0.854,0,1.547,0.694,1.547,1.548C8.551,7.881,7.858,8.574,7.004,8.574z M18.339,18.338h-2.669 v-4.177c0-0.996-0.017-2.278-1.387-2.278c-1.389,0-1.601,1.086-1.601,2.206v4.249h-2.667v-8.59h2.559v1.174h0.037 c0.356-0.675,1.227-1.387,2.526-1.387c2.703,0,3.203,1.779,3.203,4.092V18.338z"></path></svg><span class="wp-block-social-link-label screen-reader-text">LinkedIn</span></a></li>

<li class="wp-social-link wp-social-link-bluesky  wp-block-social-link"><a href="https://bsky.app/profile/weston.ruter.net/post/3lumkutpuv22l" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M6.3,4.2c2.3,1.7,4.8,5.3,5.7,7.2.9-1.9,3.4-5.4,5.7-7.2,1.7-1.3,4.3-2.2,4.3.9s-.4,5.2-.6,5.9c-.7,2.6-3.3,3.2-5.6,2.8,4,.7,5.1,3,2.9,5.3-5,5.2-6.7-2.8-6.7-2.8,0,0-1.7,8-6.7,2.8-2.2-2.3-1.2-4.6,2.9-5.3-2.3.4-4.9-.3-5.6-2.8-.2-.7-.6-5.3-.6-5.9,0-3.1,2.7-2.1,4.3-.9h0Z"></path></svg><span class="wp-block-social-link-label screen-reader-text">Bluesky</span></a></li>

<li class="wp-social-link wp-social-link-mastodon  wp-block-social-link"><a href="https://mastodon.social/@westonruter/114901525584434540" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M23.193 7.879c0-5.206-3.411-6.732-3.411-6.732C18.062.357 15.108.025 12.041 0h-.076c-3.068.025-6.02.357-7.74 1.147 0 0-3.411 1.526-3.411 6.732 0 1.192-.023 2.618.015 4.129.124 5.092.934 10.109 5.641 11.355 2.17.574 4.034.695 5.535.612 2.722-.15 4.25-.972 4.25-.972l-.09-1.975s-1.945.613-4.129.539c-2.165-.074-4.449-.233-4.799-2.891a5.499 5.499 0 0 1-.048-.745s2.125.52 4.817.643c1.646.075 3.19-.097 4.758-.283 3.007-.359 5.625-2.212 5.954-3.905.517-2.665.475-6.507.475-6.507zm-4.024 6.709h-2.497V8.469c0-1.29-.543-1.944-1.628-1.944-1.2 0-1.802.776-1.802 2.312v3.349h-2.483v-3.35c0-1.536-.602-2.312-1.802-2.312-1.085 0-1.628.655-1.628 1.944v6.119H4.832V8.284c0-1.289.328-2.313.987-3.07.68-.758 1.569-1.146 2.674-1.146 1.278 0 2.246.491 2.886 1.474L12 6.585l.622-1.043c.64-.983 1.608-1.474 2.886-1.474 1.104 0 1.994.388 2.674 1.146.658.757.986 1.781.986 3.07v6.304z"/></svg><span class="wp-block-social-link-label screen-reader-text">Mastodon</span></a></li>

<li class="wp-social-link wp-social-link-twitter  wp-block-social-link"><a href="https://x.com/westonruter/status/1947932105599815923" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M22.23,5.924c-0.736,0.326-1.527,0.547-2.357,0.646c0.847-0.508,1.498-1.312,1.804-2.27 c-0.793,0.47-1.671,0.812-2.606,0.996C18.324,4.498,17.257,4,16.077,4c-2.266,0-4.103,1.837-4.103,4.103 c0,0.322,0.036,0.635,0.106,0.935C8.67,8.867,5.647,7.234,3.623,4.751C3.27,5.357,3.067,6.062,3.067,6.814 c0,1.424,0.724,2.679,1.825,3.415c-0.673-0.021-1.305-0.206-1.859-0.513c0,0.017,0,0.034,0,0.052c0,1.988,1.414,3.647,3.292,4.023 c-0.344,0.094-0.707,0.144-1.081,0.144c-0.264,0-0.521-0.026-0.772-0.074c0.522,1.63,2.038,2.816,3.833,2.85 c-1.404,1.1-3.174,1.756-5.096,1.756c-0.331,0-0.658-0.019-0.979-0.057c1.816,1.164,3.973,1.843,6.29,1.843 c7.547,0,11.675-6.252,11.675-11.675c0-0.178-0.004-0.355-0.012-0.531C20.985,7.47,21.68,6.747,22.23,5.924z"></path></svg><span class="wp-block-social-link-label screen-reader-text">Twitter</span></a></li>

<li class="wp-social-link wp-social-link-threads  wp-block-social-link"><a href="https://www.threads.com/@westonruter/post/DMcW7QUOfrC?xmt=AQF0RmSf6xjzbuDgVlvigocp0pk-4R0eBjA7GUIEXS4bQw" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M16.3 11.3c-.1 0-.2-.1-.2-.1-.1-2.6-1.5-4-3.9-4-1.4 0-2.6.6-3.3 1.7l1.3.9c.5-.8 1.4-1 2-1 .8 0 1.4.2 1.7.7.3.3.5.8.5 1.3-.7-.1-1.4-.2-2.2-.1-2.2.1-3.7 1.4-3.6 3.2 0 .9.5 1.7 1.3 2.2.7.4 1.5.6 2.4.6 1.2-.1 2.1-.5 2.7-1.3.5-.6.8-1.4.9-2.4.6.3 1 .8 1.2 1.3.4.9.4 2.4-.8 3.6-1.1 1.1-2.3 1.5-4.3 1.5-2.1 0-3.8-.7-4.8-2S5.7 14.3 5.7 12c0-2.3.5-4.1 1.5-5.4 1.1-1.3 2.7-2 4.8-2 2.2 0 3.8.7 4.9 2 .5.7.9 1.5 1.2 2.5l1.5-.4c-.3-1.2-.8-2.2-1.5-3.1-1.3-1.7-3.3-2.6-6-2.6-2.6 0-4.7.9-6 2.6C4.9 7.2 4.3 9.3 4.3 12s.6 4.8 1.9 6.4c1.4 1.7 3.4 2.6 6 2.6 2.3 0 4-.6 5.3-2 1.8-1.8 1.7-4 1.1-5.4-.4-.9-1.2-1.7-2.3-2.3zm-4 3.8c-1 .1-2-.4-2-1.3 0-.7.5-1.5 2.1-1.6h.5c.6 0 1.1.1 1.6.2-.2 2.3-1.3 2.7-2.2 2.7z"/></svg><span class="wp-block-social-link-label screen-reader-text">Threads</span></a></li></ul>



<p></p>
<p>The post <a href="https://weston.ruter.net/2025/07/23/instant-back-forward-navigations-in-wordpress/">Instant Back/Forward Navigations in WordPress</a> appeared first on <a href="https://weston.ruter.net">Weston Ruter</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://weston.ruter.net/2025/07/23/instant-back-forward-navigations-in-wordpress/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">35588</post-id>	</item>
		<item>
		<title>Eliminating Layout Shifts in the Video Block</title>
		<link>https://weston.ruter.net/2025/06/05/eliminating-layout-shifts-in-the-video-block/</link>
					<comments>https://weston.ruter.net/2025/06/05/eliminating-layout-shifts-in-the-video-block/#respond</comments>
		
		<dc:creator><![CDATA[Weston Ruter]]></dc:creator>
		<pubDate>Thu, 05 Jun 2025 21:09:42 +0000</pubDate>
				<category><![CDATA[WordPress]]></category>
		<category><![CDATA[ai]]></category>
		<category><![CDATA[amp]]></category>
		<category><![CDATA[cls]]></category>
		<category><![CDATA[cwv]]></category>
		<guid isPermaLink="false">https://weston.ruter.net/?p=34930</guid>

					<description><![CDATA[<p>Like the rest of the internet, I&#8217;ve been awestruck by the quality of Google&#8217;s Veo 3 AI video generator (even with audio). As you&#8217;ve seen from my posts, the American bison &#x1f9ac; is my favorite animal (aside from my cat of course). Also, perhaps I&#8217;ve watched too many videos from the Mustache Farmer, but I [&#8230;]</p>
<p>The post <a href="https://weston.ruter.net/2025/06/05/eliminating-layout-shifts-in-the-video-block/">Eliminating Layout Shifts in the Video Block</a> appeared first on <a href="https://weston.ruter.net">Weston Ruter</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Like the rest of the internet, I&#8217;ve been awestruck by the quality of Google&#8217;s <a href="https://deepmind.google/models/veo/">Veo 3</a> AI video generator (even with audio). As you&#8217;ve seen from my posts, the <a href="https://en.wikipedia.org/wiki/American_bison">American bison</a> <img src="https://weston.ruter.net/wp-content/plugins/local-twemoji/images/emoji/72x72/1f9ac.png?ver=17-0-2-2" alt="🦬" class="wp-smiley" style="height: 1em; max-height: 1em;" /> is my favorite animal (aside from my cat of course). Also, perhaps I&#8217;ve watched too many <a href="https://www.instagram.com/moustache_farmer/">videos</a> from the <a href="https://www.moustache-farmer.de/en">Mustache Farmer</a>, but I realized I could use Veo 3 to realize a fantasy of being able to pal around with what in reality is a wild animal. Generating bison videos brought me a lot of joy so I had to share them. However, I faced a dilemma because I didn&#8217;t want to do so via WordPress&#8217;s Video block in its current state: it suffers from a bad case of <em>layout shiftitus</em>. Since web performance is an even greater passion of mine than the bison, before I could share the videos I did a deep dive on the problem and came up with a fix. <em>(Skip to the <a href="#videos">videos</a> below if you don&#8217;t care.)</em></p>



<p>There&#8217;s a 2-year old Gutenberg issue (<a title="Video block: Should have width height dimensions to prevent cumulative layout shift (CLS)" href="https://github.com/WordPress/gutenberg/issues/52185">#52185</a>) which reported the underlying problem here that the Video block should have <code>width</code> and <code>height</code> attributes added to prevent layout shifts. Such jank causes a poor user experience and negatively impacts the <a href="https://web.dev/articles/cls">Cumulative Layout Shift</a> (CLS) metric of <a href="https://web.dev/articles/vitals">Core Web Vitals</a> (CWV).</p>



<p>A dimensionless <code>video</code> element gets a 2:1 aspect ratio placeholder (a 300&#215;150 <a href="https://html.spec.whatwg.org/multipage/media.html#the-video-element:default-object-size">default object size</a>) until the video&#8217;s metadata is loaded, at which time a layout shift happens due to the new dimensions being applied. When there&#8217;s a <code>poster</code> attribute, the placeholder dimensions get replaced with the dimensions of the poster image once it loads, also resulting in a layout shift; lastly, if the poster image doesn&#8217;t have the exact same dimensions as the video, then a second layout shift occurs once the video starts playing.</p>



<p>Take a look at these screen recordings of a Video block without a poster and then with a poster provided (and there&#8217;s a script on this test page that starts video playback after 4 seconds):</p>



<div class="wp-block-columns is-not-stacked-on-mobile is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex" id="screen-recordings-with-layout-shifts">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-video"><video height="1080" style="aspect-ratio: 476 / 1080;aspect-ratio: 476 / 1080;" width="476" controls loop muted src="https://weston.ruter.net/wp-content/uploads/2025/06/1-no-poster-layout-shift.mp4" playsinline></video><figcaption class="wp-element-caption">Without a poster, causing a layout shift.</figcaption></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-video"><video height="1080" style="aspect-ratio: 476 / 1080;aspect-ratio: 476 / 1080;" width="476" controls loop muted src="https://weston.ruter.net/wp-content/uploads/2025/06/2-with-poster-layout-shift.mp4" playsinline></video><figcaption class="wp-element-caption">With a poster, causing a layout shift.</figcaption></figure>
</div>
</div>



<p>The layout shifts are somewhat exaggerated here because:</p>



<ol class="wp-block-list">
<li>A vertical/portrait video is used.</li>



<li>A network delay is added to slow down the loading of the poster and video (which is not unexpected on a mobile connection).</li>



<li>The poster image doesn&#8217;t have the same dimensions as the video.</li>
</ol>



<p>In any case, such layout shifts seem to occur anywhere the Video block is used to some degree. </p>



<h2 class="wp-block-heading">CLS Passing Rates</h2>



<p>Layout shifts from the Video block contribute to WordPress overall having a relatively poor passing rate for CLS. On desktop, 71% of WordPress origins have a good CLS passing rate, while on mobile the passing rate is 82%. (CLS is worse on desktop presumably because more content is on the screen at a time, meaning there are more opportunities for layout shifts to appear in the viewport.) When evaluating these CLS passing rates in terms of <a href="https://en.wikipedia.org/wiki/Academic_grading_in_the_United_States#Grade_conversion">academic letter grades</a>, <strong>WordPress is getting a B− on mobile and a C− on desktop.</strong> When comparing WordPress to other popular CMS platforms, it ranks near the bottom with only Joomla performing worse, as seen in this table sorted by desktop:</p>



<figure class="wp-block-table is-style-regular has-medium-font-size"><table><thead><tr><th>Technology</th><th><a href="https://httparchive.org/reports/techreport/tech?good-cwv-over-time=CLS&amp;client=desktop&amp;tech=ALL%2CWordPress%2CShopify%2CWix%2CSquarespace%2CJoomla%2CDrupal&amp;geo=ALL&amp;rank=ALL#comparison-good-cwvs">Desktop</a></th><th><a href="https://httparchive.org/reports/techreport/tech?good-cwv-over-time=CLS&amp;client=mobile&amp;tech=ALL%2CWordPress%2CShopify%2CWix%2CSquarespace%2CJoomla%2CDrupal&amp;geo=ALL&amp;rank=ALL#comparison-good-cwvs">Mobile</a></th></tr></thead><tbody><tr><td>Wix</td><td><mark style="background-color:rgba(0, 0, 0, 0);color:#008800" class="has-inline-color">91%</mark> (A−)</td><td><mark style="background-color:rgba(0, 0, 0, 0);color:#008800" class="has-inline-color">94%</mark> (A)</td></tr><tr><td>Shopify</td><td><mark style="background-color:rgba(0, 0, 0, 0);color:#888800" class="has-inline-color">82%</mark> (B−)</td><td><mark style="background-color:rgba(0, 0, 0, 0);color:#008800" class="has-inline-color">90%</mark> (A−)</td></tr><tr><td>Squarespace</td><td><mark style="background-color:rgba(0, 0, 0, 0);color:#880000" class="has-inline-color">77%</mark> (C+)</td><td><mark style="background-color:rgba(0, 0, 0, 0);color:#888800" class="has-inline-color">88%</mark> (B+)</td></tr><tr><td>Drupal</td><td><mark style="background-color:rgba(0, 0, 0, 0);color:#880000" class="has-inline-color">72%</mark> (C−)</td><td><mark style="background-color:rgba(0, 0, 0, 0);color:#888800" class="has-inline-color">85%</mark> (B)</td></tr><tr><td><em>(Any)</em></td><td><mark style="background-color:rgba(0, 0, 0, 0);color:#880000" class="has-inline-color">72%</mark> (C−)</td><td><mark style="background-color:rgba(0, 0, 0, 0);color:#880000" class="has-inline-color">79%</mark> (C+)</td></tr><tr><td><strong>WordPress</strong></td><td><mark style="background-color:rgba(0, 0, 0, 0);color:#880000" class="has-inline-color"><strong>71%</strong></mark> (C−)</td><td><strong><mark style="background-color:rgba(0, 0, 0, 0);color:#888800" class="has-inline-color">82%</mark></strong> (B−)</td></tr><tr><td>Joomla</td><td><mark style="background-color:rgba(0, 0, 0, 0);color:#440088" class="has-inline-color">69%</mark> (D+)</td><td><mark style="background-color:rgba(0, 0, 0, 0);color:#880000" class="has-inline-color">79%</mark> (C+)</td></tr></tbody></table></figure>



<details class="wp-block-details has-small-font-size is-layout-flow wp-block-details-is-layout-flow"><summary>Graphs of origins with good CLS over time</summary>
<div class="wp-block-columns alignwide is-layout-flex wp-container-core-columns-is-layout-0884d4d2 wp-block-columns-is-layout-flex" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center">Desktop</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bce04f9bb&quot;}" data-wp-interactive="core/image" data-wp-key="6975bce04f9bb" class="wp-block-image aligncenter size-large is-resized wp-lightbox-container" style="margin-top:0"><img loading="lazy" decoding="async" width="600" height="600" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 322px) 100vw, 322px" src="https://weston.ruter.net/wp-content/uploads/2025/06/chart-cls-desktop-svgomg.svg" alt="Graph showing good CLS over time on desktop for WordPress, Shopify, Wix, Squarespace, Joomla, and Drupal. All platforms are trending upward, but WordPress is near the bottom with Joomla at ~70%, while Wix is at the top with ~90%." class="wp-image-35018" style="width:600px;height:auto"/><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center">Mobile</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bce050022&quot;}" data-wp-interactive="core/image" data-wp-key="6975bce050022" class="wp-block-image aligncenter size-large is-resized wp-lightbox-container" style="margin-top:0"><img loading="lazy" decoding="async" width="600" height="600" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 322px) 100vw, 322px" src="https://weston.ruter.net/wp-content/uploads/2025/06/chart-cls-mobile-svgomg.svg" alt="Graph showing good CLS over time on mobile for WordPress, Shopify, Wix, Squarespace, Joomla, and Drupal. All platforms are trending upward, but WordPress is near the bottom with Joomla at ~80%, while Wix is at the top with ~95%." class="wp-image-35019" style="width:600px;height:auto"/><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>
</div>
</div>
</details>



<p>The passing rates for WordPress are unsurprisingly very close to the passing rates for the web overall (any technology) since WordPress has the largest market share by far. <strong>Whenever WordPress performs badly, the web as a whole suffers. Whenever WordPress performs well, the web as a whole improves.</strong> This was the drive behind my “scaled activation” Chrome team at Google <a href="https://weston.ruter.net/2025/05/14/a-decade-as-a-core-committer-my-wordpress-contribution-history/">when I was sponsored</a> there to work on WordPress performance full time.</p>



<p>Now, CLS in WordPress is not nearly as problematic as Largest Contentful Paint (LCP), which is getting an <strong>F grade</strong> for its passing rates of <a href="https://httparchive.org/reports/techreport/tech?good-cwv-over-time=LCP&amp;client=mobile&amp;tech=ALL%2CWordPress%2CShopify%2CWix%2CSquarespace%2CJoomla%2CDrupal&amp;geo=ALL&amp;rank=ALL#comparison-good-cwvs">54% on mobile</a> and <a href="https://httparchive.org/reports/techreport/tech?good-cwv-over-time=LCP&amp;client=desktop&amp;tech=ALL%2CWordPress%2CShopify%2CWix%2CSquarespace%2CJoomla%2CDrupal&amp;geo=ALL&amp;rank=ALL#comparison-good-cwvs">65% on desktop</a>. Because of this, improving LCP has been the primary focus for us on the WordPress Core Performance Team, and the <a href="https://make.wordpress.org/core/2023/09/19/analyzing-the-core-web-vitals-performance-impact-of-wordpress-6-3-in-the-field/">metric has improved</a> thanks in part to <a href="https://make.wordpress.org/core/2023/07/13/image-performance-enhancements-in-wordpress-6-3/">adding</a> <code>fetchpriority=high</code> to LCP-probable <code>img</code> tags, <a href="https://make.wordpress.org/core/2023/07/13/image-performance-enhancements-in-wordpress-6-3/#:~:text=Lazy%2Dloading%20issues%20addressed">adjusting</a> image lazy-loading heuristics, <a href="https://core.trac.wordpress.org/ticket/58472">optimizing</a> the emoji loader, and most recently landing <a href="https://make.wordpress.org/core/2025/03/06/speculative-loading-in-6-8/">Speculative Loading</a>. And work continues on improving LCP, for example, by <a href="https://weston.ruter.net/2025/05/26/improve-lcp-by-deprioritizing-interactivity-api-script-modules/">deprioritizing non-critical scripts</a> and by leveraging client-side metrics to more accurately prioritize images via the <a href="https://github.com/WordPress/performance/blob/trunk/plugins/optimization-detective/docs/introduction.md">Optimization Detective</a> project (see also <a href="https://weston.ruter.net/2025/02/21/boosting-performance-with-optimization-detective/">my talk</a>).</p>



<p>The other CWV metric, Interaction to Next Paint (INP), is in relatively great shape with a passing rate of <a href="https://httparchive.org/reports/techreport/tech?good-cwv-over-time=INP&amp;client=mobile&amp;tech=ALL%2CWordPress%2CShopify%2CWix%2CSquarespace%2CJoomla%2CDrupal&amp;geo=ALL&amp;rank=ALL#comparison-good-cwvs">85% on mobile</a> (B) and <a href="https://httparchive.org/reports/techreport/tech?good-cwv-over-time=INP&amp;client=desktop&amp;tech=ALL%2CWordPress%2CShopify%2CWix%2CSquarespace%2CJoomla%2CDrupal&amp;geo=ALL&amp;rank=ALL#comparison-good-cwvs">98% on desktop</a> (A+).</p>



<p>So, in parallel with the continued work to improve LCP, it&#8217;s important to not neglect WordPress&#8217;s sub-optimal CLS passing rate. Prior work to improve CLS included <a href="https://make.wordpress.org/core/2020/07/14/lazy-loading-images-in-5-5/">adding</a> <code>width</code> and <code>height</code> attributes to <code>img</code> tags for the sake of lazy-loading. There&#8217;s also a ticket (<a title="Measure layout stability metrics in performance tests" href="https://core.trac.wordpress.org/ticket/59119">#59119</a>) to measure CLS in performance tests. Additionally, a key feature of the <a href="https://wordpress.org/plugins/embed-optimizer/">Embed Optimizer</a> extension to the aforementioned Optimization Detective <a href="https://wordpress.org/plugins/optimization-detective/">plugin</a> is the <strong>reduction of layout shifts</strong> caused by embeds that resize when they load. This is commonly seen in embeds for Twitter, Bluesky, and WordPress itself. Embed Optimizer keeps track of these embeds&#8217; resized heights. Then, with these resized heights stored, Embed Optimizer sets the appropriate height on the container <code>figure</code> element as the viewport-specific <code>min-height</code> so that when the embed loads any layout shift is minimized.</p>



<p>Lastly, coming back to the impetus of this post, there&#8217;s the issue of layout shifts in the Video block.</p>



<h2 class="wp-block-heading">Fixing the Video Block</h2>



<p>Preventing layout shifts in the Video block is straightforward. As described in the <a href="https://github.com/WordPress/gutenberg/issues/52185">Gutenberg issue</a>, the <code>width</code> and <code>height</code> attributes need to be supplied on the <code>video</code> tag, although a bit more is needed than just that. When a video is uploaded to the Media Library, the metadata is <a href="https://developer.wordpress.org/reference/functions/wp_generate_attachment_metadata/">obtained</a> via the <code><a href="https://developer.wordpress.org/reference/functions/wp_read_video_metadata/">wp_read_video_metadata()</a></code> function, including its width and height. Assuming that reading the metadata was successful, these dimensions can then be injected into the <code>video</code> tag in the same way as dimensions are being <a href="https://developer.wordpress.org/reference/functions/wp_img_tag_add_width_and_height_attr/">added</a> to the <code>img</code> tag. (For external videos added by URL not uploaded into the Media Library, the dimensions could be read client-side in the block editor or they could be <a href="https://github.com/WordPress/performance/issues/10#issuecomment-2601037707">gathered via Optimization Detective on the frontend</a>.)</p>



<p>This goes full circle for me because we did something similar in <time datetime="2018-05-21">2018 (7 years ago!)</time> to <a href="https://github.com/ampproject/amp-wp/pull/1026">add dimensions to videos</a> in the <a href="https://wordpress.org/plugins/amp/">AMP plugin</a> when converting from the <code>video</code> tag to the <code><a href="https://amp.dev/documentation/components/amp-video">amp-video</a></code> component. The <a href="https://amp.dev/documentation/guides-and-tutorials/learn/spec/amphtml">AMP HTML spec</a> <a href="https://amp.dev/documentation/guides-and-tutorials/learn/amp-html-layout/#width-and-height">mandates</a> (generally) that all elements must have dimensions supplied to <a href="https://blog.amp.dev/2020/04/16/cumulative-layout-shift-in-amp/">prevent layout shifts</a>, as the first of <a href="https://amp.dev/about/mission-and-vision#:~:text=merchant%2C%20and%20advertiser.-,Design%20Principles,-These%20design%20principles">AMP&#8217;s design principles</a> is to prioritize the user experience. As I <a href="https://weston.ruter.net/2025/05/14/a-decade-as-a-core-committer-my-wordpress-contribution-history/#:~:text=Ultimately%2C%20a%20lot,improving%20CWV%20metrics.">mentioned</a> in my recent <a href="https://weston.ruter.net/2025/05/14/a-decade-as-a-core-committer-my-wordpress-contribution-history/">contribution retrospective</a>, AMP predates CWV; as part of <a href="https://blog.amp.dev/2019/05/21/contributing-back-lessons-learned-part-1/">contributing back lessons learned from AMP to the whole web</a>, it “invested in defining additional metrics that would paint a more holistic image of user perceived performance.” This included a “Layout Stability” metric which came to be known as CLS.</p>



<p>In addition to providing the <code>width</code> and <code>height</code> attributes, for the <code>video</code> to scale to fit its container and to have the correct aspect ratio, the element needs to be styled with <code>height:auto</code> since it has <code>width:100%</code>. Finally, because of an <a title="[css-sizing-4] aspect-ratio via width and height attributes doesn't work for &lt;video&gt;" href="https://github.com/w3c/csswg-drafts/issues/7524">issue</a> in the CSS spec (as <a href="https://jakearchibald.com/2022/img-aspect-ratio/#:~:text=Update%3A%20Although%20browsers%20implemented%20the%20feature%20for%20%3Cvideo%3E%20as%20per%20the%20spec%2C%20the%20spec%20is%20broken%2C%20so%20it%20doesn%27t%20work%20in%20practice.">highlighted</a> by <a href="https://jakearchibald.com/">Jake Archibald</a>), the width and height need to be replicated in an <code><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/aspect-ratio">aspect-ratio</a></code> style. This currently has to be added as an inline style since the following desired use of the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/attr"><code>attr()</code></a> function in a style rule is currently only <a href="https://caniuse.com/css3-attr">supported</a> in Chromium:</p>


<pre class="wp-block-code has-medium-font-size"><span><code class="hljs language-css"><span class="hljs-selector-class">.wp-block-video</span> <span class="hljs-selector-tag">video</span><span class="hljs-selector-attr">&#91;width]</span><span class="hljs-selector-attr">&#91;height]</span> {
	<span class="hljs-attribute">aspect-ratio</span>:
		<span class="hljs-built_in">attr</span>(width  type(&lt;number&gt;)) /
		<span class="hljs-built_in">attr</span>(height type(&lt;number&gt;));
}</code></span></pre>


<p>I&#8217;ve submitted the fix in Gutenberg pull request #70293: <a href="https://github.com/WordPress/gutenberg/pull/70293">Fix layout shift caused by video tag in Video block lacking width and height</a>.</p>



<p>And since I didn&#8217;t want to wait for that fix to be merged and available in a new Gutenberg release, I also adapted it into a standalone <a href="https://github.com/westonruter/layout-stabilized-video-block">Layout-stabilized Video Block</a> plugin which is active here on my blog (since I wanted to share those AI-generated bison videos!). Please install the plugin on your site and test how it works with your Video blocks.</p>



<p>Compare the <a href="#screen-recordings-with-layout-shifts">above screen recordings</a> of layout-shifting Video blocks with the following screen recordings where the fix is applied:</p>



<div class="wp-block-columns is-not-stacked-on-mobile is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex" id="screen-recordings-without-layout-shifts">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-video"><video height="1080" style="aspect-ratio: 476 / 1080;aspect-ratio: 476 / 1080;" width="476" controls loop muted src="https://weston.ruter.net/wp-content/uploads/2025/06/3-no-poster-and-no-layout-shift.mp4" playsinline></video><figcaption class="wp-element-caption">Without a poster, not causing a layout shift.</figcaption></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-video"><video height="1080" style="aspect-ratio: 476 / 1080;aspect-ratio: 476 / 1080;" width="476" controls loop muted src="https://weston.ruter.net/wp-content/uploads/2025/06/4-with-poster-and-no-layout-shift.mp4" playsinline></video><figcaption class="wp-element-caption">With a poster, not causing a layout shift.</figcaption></figure>
</div>
</div>



<details class="wp-block-details has-small-font-size is-layout-flow wp-block-details-is-layout-flow"><summary>Aside: Command used for transcoding screen recordings</summary>
<p>I was really impressed with how well FFmpeg compressed the original Quicktime screen recordings from ~32MB down to just ~400KB, and since the args to <code>ffmpeg</code> are always something I have to re-discover, here&#8217;s the command for (my) future reference:</p>


<pre class="wp-block-code"><span><code class="hljs language-bash"><span class="hljs-keyword">for</span> video <span class="hljs-keyword">in</span> *.mov; <span class="hljs-keyword">do</span>
  ffmpeg \
    -i <span class="hljs-string">"<span class="hljs-variable">$video</span>"</span> \
    -vf <span class="hljs-string">"scale=-2:1080"</span> \
    -c:v libx264 \
    -preset medium \
    -crf 30 \
    -an \
    -b:a 128k \
    -movflags \
    +faststart \
    <span class="hljs-string">"<span class="hljs-variable">${video/.mov/.mp4}</span>"</span>
<span class="hljs-keyword">done</span></code></span></pre></details>



<p>You can <a href="https://weston.ruter.net/?p=34930&amp;disable_layout_stabilized_video_block=1#screen-recordings-without-layout-shifts">try loading this post without the fix</a> by adding a special query var to the URL (and then keep hard reloading to see more layout instability).</p>



<p>And now, with the layout shift fixed, let&#8217;s get to the bison videos.</p>



<h2 class="wp-block-heading" id="videos">AI Bison Videos</h2>



<p>The following Veo-generated videos were downloaded from Gemini and uploaded without any transcoding, although they are encoded quite well for the web at ~3MB each for 8 seconds of high quality 720p video with audio. I manually selected a representative frame of each video to create the poster images.</p>



<aside class="wp-block-group is-style-section-1 has-global-padding is-layout-constrained wp-container-core-group-is-layout-c385debf wp-block-group-is-layout-constrained is-style-section-1--3" style="padding-top:var(--wp--preset--spacing--20);padding-right:var(--wp--preset--spacing--20);padding-bottom:var(--wp--preset--spacing--20);padding-left:var(--wp--preset--spacing--20)">
<p class="is-style-default">Bonus: This site is running the <a href="https://wordpress.org/plugins/image-prioritizer/">Image Prioritizer</a> extension of Optimization Detective which improves the loading of videos by:</p>



<ol class="wp-block-list">
<li>Lazy loading the videos by setting the <code>preload</code> initially to <code>none</code>, and then removing this when the video enters the viewport.</li>



<li>Lazy loading the poster images.</li>



<li>Reducing the size of the poster image according to the max width of the <code>video</code> across all viewport sizes.</li>



<li>Preloading poster images for LCP <code>video</code> elements (not relevant for this post).</li>
</ol>
</aside>



<p>This first video leans a little too hard into the “mustache” of the Mustache Farmer:</p>



<figure class="wp-block-video"><video height="720" style="aspect-ratio: 1280 / 720;aspect-ratio: 1280 / 720;" width="1280" controls poster="https://weston.ruter.net/wp-content/uploads/2025/06/Video_Ready_Bison_and_Man.jpg" src="https://weston.ruter.net/wp-content/uploads/2025/06/Video_Ready_Bison_and_Man.mp4"></video><figcaption class="wp-element-caption">Prompt: A bison falls asleep in the lap of a man in the style of the Mustache Farmer.</figcaption></figure>



<p>I like how this guy seemingly pretends he didn&#8217;t know where the bison was in the open field, “Oh, there you are buddy!”:</p>



<figure class="wp-block-video"><video height="720" style="aspect-ratio: 1280 / 720;aspect-ratio: 1280 / 720;" width="1280" controls poster="https://weston.ruter.net/wp-content/uploads/2025/06/Bison_Nuzzles_Man_Video_Ready.jpg" src="https://weston.ruter.net/wp-content/uploads/2025/06/Bison_Nuzzles_Man_Video_Ready.mp4"></video><figcaption class="wp-element-caption">Prompt: A bison runs over to a man and affectionately nuzzles him, putting his head next to the man to rub up against him. The man pets the bison&#8217;s head and smiles. The bison is the man&#8217;s pet.</figcaption></figure>



<p>Awkward running:</p>



<figure class="wp-block-video"><video height="720" style="aspect-ratio: 1280 / 720;aspect-ratio: 1280 / 720;" width="1280" controls poster="https://weston.ruter.net/wp-content/uploads/2025/06/Bison_Nuzzles_Petting_Man_Video.jpg" src="https://weston.ruter.net/wp-content/uploads/2025/06/Bison_Nuzzles_Petting_Man_Video.mp4"></video><figcaption class="wp-element-caption">Prompt: A bison and a man run toward each other. When they meet, the bison affectionately nuzzles him, putting his head next to the man to rub up against him. The man pets the bison&#8217;s head and smiles. The bison is the man&#8217;s pet.</figcaption></figure>



<p>A somewhat less awkward run:</p>



<figure class="wp-block-video"><video height="720" style="aspect-ratio: 1280 / 720;aspect-ratio: 1280 / 720;" width="1280" controls poster="https://weston.ruter.net/wp-content/uploads/2025/06/Bison_and_Man_s_Affectionate_Meeting.jpg" src="https://weston.ruter.net/wp-content/uploads/2025/06/Bison_and_Man_s_Affectionate_Meeting.mp4"></video><figcaption class="wp-element-caption">Prompt: A bison and a man run toward each other. When they meet, the bison affectionately nuzzles him, putting his head next to the man to rub up against him. The man pets the bison&#8217;s head and smiles. The bison is the man&#8217;s pet.</figcaption></figure>



<p>There&#8217;s an invisible stirrup in this one:</p>



<figure class="wp-block-video"><video height="720" style="aspect-ratio: 1280 / 720;aspect-ratio: 1280 / 720;" width="1280" controls poster="https://weston.ruter.net/wp-content/uploads/2025/06/Bison_Ride_Video_Ready.jpg" src="https://weston.ruter.net/wp-content/uploads/2025/06/Bison_Ride_Video_Ready.mp4"></video><figcaption class="wp-element-caption">Prompt: A bison kneels down for a man, and the man climbs on the back of the bison and they gallop off into the sunset.</figcaption></figure>



<p>Apparently Tom Cruise with maniacal laughter and a magically-appearing saddle:</p>



<figure class="wp-block-video"><video height="720" style="aspect-ratio: 1280 / 720;aspect-ratio: 1280 / 720;" width="1280" controls poster="https://weston.ruter.net/wp-content/uploads/2025/06/Bison_Ride_Video_Ready_.jpg" src="https://weston.ruter.net/wp-content/uploads/2025/06/Bison_Ride_Video_Ready_.mp4"></video><figcaption class="wp-element-caption">Prompt: A bison kneels down for a man to mount on top of the bison. The bison wants to give the man a ride. The man laughs and smiles as he climbs onto the back of the bison and they gallop off into the sunset.</figcaption></figure>



<p>Just heartwarming:</p>



<figure class="wp-block-video"><video height="720" style="aspect-ratio: 1280 / 720;aspect-ratio: 1280 / 720;" width="1280" controls poster="https://weston.ruter.net/wp-content/uploads/2025/06/Bison_and_Man_s_Affectionate_Moment.jpg" src="https://weston.ruter.net/wp-content/uploads/2025/06/Bison_and_Man_s_Affectionate_Moment.mp4"></video><figcaption class="wp-element-caption">Prompt: A bison walks up to a man. The bison rubs his head against the man, nuzzling him like a cat. The man affectionately rubs the bison&#8217;s head. The man smiles. The bison could be the man&#8217;s pet.</figcaption></figure>



<p>The guy&#8217;s smile is a little intense in this one, but it&#8217;s also heartwarming:</p>



<figure class="wp-block-video"><video height="720" style="aspect-ratio: 1280 / 720;aspect-ratio: 1280 / 720;" width="1280" controls poster="https://weston.ruter.net/wp-content/uploads/2025/06/Video_of_Man_and_Bison.jpg" src="https://weston.ruter.net/wp-content/uploads/2025/06/Video_of_Man_and_Bison.mp4"></video><figcaption class="wp-element-caption">Prompt: A man and a bison having fun together, rolling in the grass and wrestling. The bison is the man&#8217;s pet.</figcaption></figure>



<p>This guy seems a little fake (almost like he&#8217;s AI):</p>



<figure class="wp-block-video"><video height="720" style="aspect-ratio: 1280 / 720;aspect-ratio: 1280 / 720;" width="1280" controls poster="https://weston.ruter.net/wp-content/uploads/2025/06/Bison_Petting_Video_Ready_.jpg" src="https://weston.ruter.net/wp-content/uploads/2025/06/Bison_Petting_Video_Ready_.mp4"></video><figcaption class="wp-element-caption">Prompt: A bison is standing close to a man. The man scratches the bison under its neck and he pets the hair on the bison&#8217;s head. The bison is enjoying the affection. The man smiles and tells the bison, &#8220;You&#8217;re such a good boy.&#8221; The bison likes the man. The bison is the man&#8217;s pet.</figcaption></figure>



<p>Nice purring sound effect, followed by flapping bird wings?</p>



<figure class="wp-block-video"><video height="720" style="aspect-ratio: 1280 / 720;aspect-ratio: 1280 / 720;" width="1280" controls poster="https://weston.ruter.net/wp-content/uploads/2025/06/Cat_and_Bison_Video_Link.jpg" src="https://weston.ruter.net/wp-content/uploads/2025/06/Cat_and_Bison_Video_Link.mp4"></video><figcaption class="wp-element-caption">Prompt: A gray tabby cat with faint stripes jumps on top of a bison and starts to knead its paws into the bison&#8217;s thick fur. The bison likes it. The bison and the cat are friends.</figcaption></figure>



<p>Cozy, but the cat glitches a bit at the end:</p>



<figure class="wp-block-video"><video height="720" style="aspect-ratio: 1280 / 720;aspect-ratio: 1280 / 720;" width="1280" controls poster="https://weston.ruter.net/wp-content/uploads/2025/06/Video_Bison_and_Cat_Nap.jpg" src="https://weston.ruter.net/wp-content/uploads/2025/06/Video_Bison_and_Cat_Nap.mp4"></video><figcaption class="wp-element-caption">Prompt: A bison is lying down in the grass. A gray tabby cat nuzzles up to the bison and climbs on its back to lie down for a nap. The bison likes the cat&#8217;s company.</figcaption></figure>



<p>Now I&#8217;m going to go pet my real cat. <img src="https://weston.ruter.net/wp-content/plugins/local-twemoji/images/emoji/72x72/1f638.png?ver=17-0-2-2" alt="😸" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="has-medium-font-size">Where I&#8217;ve shared this:</p>



<ul class="wp-block-social-links is-layout-flex wp-block-social-links-is-layout-flex"><li class="wp-social-link wp-social-link-linkedin  wp-block-social-link"><a href="https://www.linkedin.com/posts/westonruter_eliminating-layout-shifts-in-the-video-block-activity-7336504092174729217-HmKO" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M19.7,3H4.3C3.582,3,3,3.582,3,4.3v15.4C3,20.418,3.582,21,4.3,21h15.4c0.718,0,1.3-0.582,1.3-1.3V4.3 C21,3.582,20.418,3,19.7,3z M8.339,18.338H5.667v-8.59h2.672V18.338z M7.004,8.574c-0.857,0-1.549-0.694-1.549-1.548 c0-0.855,0.691-1.548,1.549-1.548c0.854,0,1.547,0.694,1.547,1.548C8.551,7.881,7.858,8.574,7.004,8.574z M18.339,18.338h-2.669 v-4.177c0-0.996-0.017-2.278-1.387-2.278c-1.389,0-1.601,1.086-1.601,2.206v4.249h-2.667v-8.59h2.559v1.174h0.037 c0.356-0.675,1.227-1.387,2.526-1.387c2.703,0,3.203,1.779,3.203,4.092V18.338z"></path></svg><span class="wp-block-social-link-label screen-reader-text">LinkedIn</span></a></li>

<li class="wp-social-link wp-social-link-bluesky  wp-block-social-link"><a href="https://bsky.app/profile/weston.ruter.net/post/3lqvbcqwx622q" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M6.3,4.2c2.3,1.7,4.8,5.3,5.7,7.2.9-1.9,3.4-5.4,5.7-7.2,1.7-1.3,4.3-2.2,4.3.9s-.4,5.2-.6,5.9c-.7,2.6-3.3,3.2-5.6,2.8,4,.7,5.1,3,2.9,5.3-5,5.2-6.7-2.8-6.7-2.8,0,0-1.7,8-6.7,2.8-2.2-2.3-1.2-4.6,2.9-5.3-2.3.4-4.9-.3-5.6-2.8-.2-.7-.6-5.3-.6-5.9,0-3.1,2.7-2.1,4.3-.9h0Z"></path></svg><span class="wp-block-social-link-label screen-reader-text">Bluesky</span></a></li>

<li class="wp-social-link wp-social-link-mastodon  wp-block-social-link"><a href="https://mastodon.social/@westonruter/114632880406289198" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M23.193 7.879c0-5.206-3.411-6.732-3.411-6.732C18.062.357 15.108.025 12.041 0h-.076c-3.068.025-6.02.357-7.74 1.147 0 0-3.411 1.526-3.411 6.732 0 1.192-.023 2.618.015 4.129.124 5.092.934 10.109 5.641 11.355 2.17.574 4.034.695 5.535.612 2.722-.15 4.25-.972 4.25-.972l-.09-1.975s-1.945.613-4.129.539c-2.165-.074-4.449-.233-4.799-2.891a5.499 5.499 0 0 1-.048-.745s2.125.52 4.817.643c1.646.075 3.19-.097 4.758-.283 3.007-.359 5.625-2.212 5.954-3.905.517-2.665.475-6.507.475-6.507zm-4.024 6.709h-2.497V8.469c0-1.29-.543-1.944-1.628-1.944-1.2 0-1.802.776-1.802 2.312v3.349h-2.483v-3.35c0-1.536-.602-2.312-1.802-2.312-1.085 0-1.628.655-1.628 1.944v6.119H4.832V8.284c0-1.289.328-2.313.987-3.07.68-.758 1.569-1.146 2.674-1.146 1.278 0 2.246.491 2.886 1.474L12 6.585l.622-1.043c.64-.983 1.608-1.474 2.886-1.474 1.104 0 1.994.388 2.674 1.146.658.757.986 1.781.986 3.07v6.304z"/></svg><span class="wp-block-social-link-label screen-reader-text">Mastodon</span></a></li>

<li class="wp-social-link wp-social-link-twitter  wp-block-social-link"><a href="https://x.com/westonruter/status/1930738990308884546" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M22.23,5.924c-0.736,0.326-1.527,0.547-2.357,0.646c0.847-0.508,1.498-1.312,1.804-2.27 c-0.793,0.47-1.671,0.812-2.606,0.996C18.324,4.498,17.257,4,16.077,4c-2.266,0-4.103,1.837-4.103,4.103 c0,0.322,0.036,0.635,0.106,0.935C8.67,8.867,5.647,7.234,3.623,4.751C3.27,5.357,3.067,6.062,3.067,6.814 c0,1.424,0.724,2.679,1.825,3.415c-0.673-0.021-1.305-0.206-1.859-0.513c0,0.017,0,0.034,0,0.052c0,1.988,1.414,3.647,3.292,4.023 c-0.344,0.094-0.707,0.144-1.081,0.144c-0.264,0-0.521-0.026-0.772-0.074c0.522,1.63,2.038,2.816,3.833,2.85 c-1.404,1.1-3.174,1.756-5.096,1.756c-0.331,0-0.658-0.019-0.979-0.057c1.816,1.164,3.973,1.843,6.29,1.843 c7.547,0,11.675-6.252,11.675-11.675c0-0.178-0.004-0.355-0.012-0.531C20.985,7.47,21.68,6.747,22.23,5.924z"></path></svg><span class="wp-block-social-link-label screen-reader-text">Twitter</span></a></li>

<li class="wp-social-link wp-social-link-threads  wp-block-social-link"><a href="https://www.threads.com/@westonruter/post/DKmwlxgOHzL" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M16.3 11.3c-.1 0-.2-.1-.2-.1-.1-2.6-1.5-4-3.9-4-1.4 0-2.6.6-3.3 1.7l1.3.9c.5-.8 1.4-1 2-1 .8 0 1.4.2 1.7.7.3.3.5.8.5 1.3-.7-.1-1.4-.2-2.2-.1-2.2.1-3.7 1.4-3.6 3.2 0 .9.5 1.7 1.3 2.2.7.4 1.5.6 2.4.6 1.2-.1 2.1-.5 2.7-1.3.5-.6.8-1.4.9-2.4.6.3 1 .8 1.2 1.3.4.9.4 2.4-.8 3.6-1.1 1.1-2.3 1.5-4.3 1.5-2.1 0-3.8-.7-4.8-2S5.7 14.3 5.7 12c0-2.3.5-4.1 1.5-5.4 1.1-1.3 2.7-2 4.8-2 2.2 0 3.8.7 4.9 2 .5.7.9 1.5 1.2 2.5l1.5-.4c-.3-1.2-.8-2.2-1.5-3.1-1.3-1.7-3.3-2.6-6-2.6-2.6 0-4.7.9-6 2.6C4.9 7.2 4.3 9.3 4.3 12s.6 4.8 1.9 6.4c1.4 1.7 3.4 2.6 6 2.6 2.3 0 4-.6 5.3-2 1.8-1.8 1.7-4 1.1-5.4-.4-.9-1.2-1.7-2.3-2.3zm-4 3.8c-1 .1-2-.4-2-1.3 0-.7.5-1.5 2.1-1.6h.5c.6 0 1.1.1 1.6.2-.2 2.3-1.3 2.7-2.2 2.7z"/></svg><span class="wp-block-social-link-label screen-reader-text">Threads</span></a></li></ul>



<p></p>
<p>The post <a href="https://weston.ruter.net/2025/06/05/eliminating-layout-shifts-in-the-video-block/">Eliminating Layout Shifts in the Video Block</a> appeared first on <a href="https://weston.ruter.net">Weston Ruter</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://weston.ruter.net/2025/06/05/eliminating-layout-shifts-in-the-video-block/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		<enclosure url="https://weston.ruter.net/wp-content/uploads/2025/06/1-no-poster-layout-shift.mp4" length="300754" type="video/mp4" />
<enclosure url="https://weston.ruter.net/wp-content/uploads/2025/06/2-with-poster-layout-shift.mp4" length="408801" type="video/mp4" />
<enclosure url="https://weston.ruter.net/wp-content/uploads/2025/06/3-no-poster-and-no-layout-shift.mp4" length="344880" type="video/mp4" />
<enclosure url="https://weston.ruter.net/wp-content/uploads/2025/06/4-with-poster-and-no-layout-shift.mp4" length="305708" type="video/mp4" />
<enclosure url="https://weston.ruter.net/wp-content/uploads/2025/06/Video_Ready_Bison_and_Man.mp4" length="2067831" type="video/mp4" />
<enclosure url="https://weston.ruter.net/wp-content/uploads/2025/06/Bison_Nuzzles_Man_Video_Ready.mp4" length="2408261" type="video/mp4" />
<enclosure url="https://weston.ruter.net/wp-content/uploads/2025/06/Bison_Nuzzles_Petting_Man_Video.mp4" length="4080045" type="video/mp4" />
<enclosure url="https://weston.ruter.net/wp-content/uploads/2025/06/Bison_and_Man_s_Affectionate_Meeting.mp4" length="2752539" type="video/mp4" />
<enclosure url="https://weston.ruter.net/wp-content/uploads/2025/06/Bison_Ride_Video_Ready.mp4" length="1430343" type="video/mp4" />
<enclosure url="https://weston.ruter.net/wp-content/uploads/2025/06/Bison_Ride_Video_Ready_.mp4" length="3252186" type="video/mp4" />
<enclosure url="https://weston.ruter.net/wp-content/uploads/2025/06/Bison_and_Man_s_Affectionate_Moment.mp4" length="2346916" type="video/mp4" />
<enclosure url="https://weston.ruter.net/wp-content/uploads/2025/06/Video_of_Man_and_Bison.mp4" length="3209981" type="video/mp4" />
<enclosure url="https://weston.ruter.net/wp-content/uploads/2025/06/Bison_Petting_Video_Ready_.mp4" length="2131074" type="video/mp4" />
<enclosure url="https://weston.ruter.net/wp-content/uploads/2025/06/Cat_and_Bison_Video_Link.mp4" length="2164898" type="video/mp4" />
<enclosure url="https://weston.ruter.net/wp-content/uploads/2025/06/Video_Bison_and_Cat_Nap.mp4" length="2180601" type="video/mp4" />

		<post-id xmlns="com-wordpress:feed-additions:1">34930</post-id>	</item>
		<item>
		<title>Improve LCP by Deprioritizing  Script Modules from the Interactivity API</title>
		<link>https://weston.ruter.net/2025/05/26/improve-lcp-by-deprioritizing-interactivity-api-script-modules/</link>
					<comments>https://weston.ruter.net/2025/05/26/improve-lcp-by-deprioritizing-interactivity-api-script-modules/#respond</comments>
		
		<dc:creator><![CDATA[Weston Ruter]]></dc:creator>
		<pubDate>Mon, 26 May 2025 20:36:59 +0000</pubDate>
				<category><![CDATA[WordPress]]></category>
		<category><![CDATA[interactivity api]]></category>
		<category><![CDATA[lcp]]></category>
		<category><![CDATA[performance]]></category>
		<guid isPermaLink="false">https://weston.ruter.net/?p=34457</guid>

					<description><![CDATA[<p>Adding a fetchpriority of low to script modules and moving them from the head to the footer can improve LCP by >9%! I've written plugins you can use to implement this now while waiting for them to land in WordPress core.</p>
<p>The post <a href="https://weston.ruter.net/2025/05/26/improve-lcp-by-deprioritizing-interactivity-api-script-modules/">Improve LCP by Deprioritizing  Script Modules from the Interactivity API</a> appeared first on <a href="https://weston.ruter.net">Weston Ruter</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="is-style-text-subtitle is-style-text-subtitle--4">Adding a <code>fetchpriority</code> of <code>low</code> to script modules and moving them from the <code>head</code> to the footer can improve <abbr title="Largest Contentful Paint">LCP</abbr> by &gt;9%! I&#8217;ve <a title="Script Fetch Priority Low" href="https://github.com/westonruter/script-fetchpriority-low">written</a> <a title="Script Modules in Footer" href="https://github.com/westonruter/script-modules-in-footer">plugins</a> you can use to implement this now while waiting for them to land in WordPress core.</p>



<p class="has-background" style="background-color:#c0ffc0"><strong>Update 2025-09-03:</strong> In commit <a href="https://core.trac.wordpress.org/changeset/60704">r60704</a>, scripts and script modules support registration with <code>fetchpriority</code>, and the Interactivity API script modules and <code>comment-reply</code> script both use <code>low</code> by default. This is milestoned for WP 6.9 (scheduled for December 2025), but you can use a <a href="https://github.com/westonruter/script-fetchpriority-low">plugin</a> in the meantime.</p>



<p>The past week I&#8217;ve been doing a deep dive into the performance impact of how WordPress loads script modules for the <a href="https://developer.wordpress.org/block-editor/reference-guides/interactivity-api/">Interactivity API</a>. When the Interactivity API was first introduced, it used classic non-module scripts which were printed at the end of the <code>body</code> (i.e. in the footer, at <code>wp_footer</code>) so that they would not block the <a href="https://developer.mozilla.org/en-US/docs/Web/Performance/Guides/Critical_rendering_path">critical rendering path</a>. With support for <a href="https://make.wordpress.org/core/2023/07/14/registering-scripts-with-async-and-defer-attributes-in-wordpress-6-3/">script loading strategies</a> in core, I <a href="https://github.com/WordPress/gutenberg/pull/52536">added</a> <code>defer</code> to these scripts and moved them to the <code>head</code> (i.e. at <code>wp_head</code>) in block themes, <a href="https://core.trac.wordpress.org/ticket/59115#:~:text=Leaving%20them%20in%20the%20head%20is%20advantageous%20because%20it%20means%20the%20browser%20will%20discover%20these%20scripts%20earlier%20and%20start%20loading%20them%20with%20other%20page%20resources%2C%20but%20the%20presence%20of%20defer%20means%20that%20they%20will%20no%20longer%20block%20page%20rendering.">reasoning</a>:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Leaving them in the <code>head</code> is advantageous because it means the browser will discover these scripts earlier and start loading them with other page resources, but the presence of <code>defer</code> means that they will no longer block page rendering.</p>
</blockquote>



<p>Ultimately, when the Interactivity API was <a href="https://make.wordpress.org/core/2024/03/04/interactivity-api-dev-note/">fully launched</a> in WordPress 6.5, it had switched to using <a href="https://make.wordpress.org/core/2024/03/04/script-modules-in-6-5/">script modules</a>. One great thing about script modules is that they don&#8217;t block rendering: they have the <code>defer</code> behavior <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#:~:text=There%20is%20no%20need%20to%20use%20the%20defer%20attribute%20(see%20%3Cscript%3E%20attributes)%20when%20loading%20a%20module%20script%3B%20modules%20are%20deferred%20automatically.">by default</a>. This means that they are safe to place in the <code>head</code>, and they continue to be printed there in block themes. (In classic themes, the block scripts are printed in the footer since blocks aren&#8217;t parsed before <code>wp_head</code> runs.) However, as I&#8217;ve been researching the past week, there can still be negative implications to the LCP metric when these script modules are discovered early, even though they don&#8217;t block rendering.</p>



<p>I set up a vanilla test site with Local WP and the Twenty Twenty-Five default theme active. I configured the theme template and post content so that all block configurations which depend on the Interactivity API are present:</p>



<ul class="wp-block-list">
<li>The theme template has a <strong>Navigation</strong> block with the mobile overlay menu enabled (which is the default).</li>



<li>A <strong>Search</strong> block is added to the header with the “Button only” configuration which depends on the Interactivity API.</li>



<li>The <a href="https://gist.github.com/westonruter/9ef0dfa42e4237b8a823e4b6803d3d7a#file-post_content-html">post content</a> starts with an <strong>Image</strong> block whose <code>IMG</code> is the LCP element. It is configured to “Enlarge on click”.<sup data-fn="c7a2be25-eace-4671-8089-f9286a5703ea" class="fn"><a href="#c7a2be25-eace-4671-8089-f9286a5703ea" id="c7a2be25-eace-4671-8089-f9286a5703ea-link">1</a></sup> This uses a <a href="https://en.wikipedia.org/wiki/File:American_bison_k5680-1.jpg">photo</a> of a Bison of course. <img src="https://weston.ruter.net/wp-content/plugins/local-twemoji/images/emoji/72x72/1f9ac.png?ver=17-0-2-2" alt="🦬" class="wp-smiley" style="height: 1em; max-height: 1em;" /></li>



<li>After a few paragraphs of Lorem Ipsum, I added a <strong>File</strong> block for a PDF with “Show inline embed” enabled.</li>



<li>The “More Posts”&nbsp;section of the template has a <strong>Query Loop</strong> which has its “Reload full page” advanced setting turned off.</li>
</ul>



<p>In the end, this results in the following markup being printed in the <code>head</code> (from the <a href="https://gist.github.com/westonruter/9ef0dfa42e4237b8a823e4b6803d3d7a#file-before-html">full page source</a> with some prettying): </p>


<pre class="wp-block-code"><span><code class="hljs language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"importmap"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"wp-importmap"</span>&gt;</span><span class="actionscript">
{
	<span class="hljs-string">"imports"</span>:{
		<span class="hljs-string">"@wordpress/interactivity"</span>: <span class="hljs-string">"/wp-includes/js/dist/script-modules/interactivity/index.min.js?ver=55aebb6e0a16726baffb"</span>,
		<span class="hljs-string">"@wordpress/interactivity-router"</span>: <span class="hljs-string">"/wp-includes/js/dist/script-modules/interactivity-router/index.min.js?ver=dc4a227f142d2e68ef83"</span>,
		<span class="hljs-string">"@wordpress/a11y"</span>: <span class="hljs-string">"/wp-includes/js/dist/script-modules/a11y/index.min.js?ver=b7d06936b8bc23cff2ad"</span>
	}
}
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/wp-includes/js/dist/script-modules/block-library/file/view.min.js?ver=fdc2f6842e015af83140"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"@wordpress/block-library/file/view-js-module"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/wp-includes/js/dist/script-modules/block-library/image/view.min.js?ver=e38a2f910342023b9d19"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"@wordpress/block-library/image/view-js-module"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/wp-includes/js/dist/script-modules/block-library/navigation/view.min.js?ver=61572d447d60c0aa5240"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"@wordpress/block-library/navigation/view-js-module"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/wp-includes/js/dist/script-modules/block-library/query/view.min.js?ver=f55e93a1ad4806e91785"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"@wordpress/block-library/query/view-js-module"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/wp-includes/js/dist/script-modules/block-library/search/view.min.js?ver=208bf143e4074549fa89"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"@wordpress/block-library/search/view-js-module"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"modulepreload"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/wp-includes/js/dist/script-modules/interactivity/index.min.js?ver=55aebb6e0a16726baffb"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"@wordpress/interactivity-js-modulepreload"</span>&gt;</span></code></span></pre>


<p>When loading the page in Chrome, here&#8217;s the network panel in DevTools:</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bce06579f&quot;}" data-wp-interactive="core/image" data-wp-key="6975bce06579f" class="wp-block-image aligncenter size-large wp-lightbox-container"><img data-recalc-dims="1" loading="lazy" decoding="async" width="501" height="700" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 501px) 100vw, 501px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/chrome-network-panel-script-modules-default-priority-two-columns.png?resize=501%2C700&#038;ssl=1" alt="Screenshot of the Chrome DevTools network panel where the script module resources are being loaded with high priority before the Bison image (for the LCP element)" class="wp-image-34758" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/chrome-network-panel-script-modules-default-priority-two-columns.png?resize=501%2C700&amp;ssl=1 501w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/chrome-network-panel-script-modules-default-priority-two-columns.png?resize=215%2C300&amp;ssl=1 215w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/chrome-network-panel-script-modules-default-priority-two-columns.png?resize=768%2C1072&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/chrome-network-panel-script-modules-default-priority-two-columns.png?resize=1100%2C1536&amp;ssl=1 1100w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/chrome-network-panel-script-modules-default-priority-two-columns.png?resize=1467%2C2048&amp;ssl=1 1467w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/chrome-network-panel-script-modules-default-priority-two-columns.png?resize=150%2C209&amp;ssl=1 150w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/chrome-network-panel-script-modules-default-priority-two-columns.png?w=1510&amp;ssl=1 1510w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<p>Notice how the script modules are being loaded with a <strong>high</strong> priority and <em>before</em> the all-important Bison image resource is loaded for the LCP element. This is bad. Here&#8217;s a view of the waterfall in the Performance panel, where you can see the script modules indeed start loading before the Bison image:</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bce066562&quot;}" data-wp-interactive="core/image" data-wp-key="6975bce066562" class="wp-block-image alignwide size-large wp-lightbox-container"><img data-recalc-dims="1" loading="lazy" decoding="async" width="700" height="187" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 1340px) 100vw, 1340px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/chrome-devtools-waterfall-script-modules-default-priority.png?resize=700%2C187&#038;ssl=1" alt="Network waterfall in Chrome DevTools showing script modules loading before the LCP image resource." class="wp-image-34739" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/chrome-devtools-waterfall-script-modules-default-priority.png?resize=700%2C187&amp;ssl=1 700w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/chrome-devtools-waterfall-script-modules-default-priority.png?resize=300%2C80&amp;ssl=1 300w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/chrome-devtools-waterfall-script-modules-default-priority.png?resize=768%2C206&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/chrome-devtools-waterfall-script-modules-default-priority.png?resize=1536%2C411&amp;ssl=1 1536w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/chrome-devtools-waterfall-script-modules-default-priority.png?resize=2048%2C549&amp;ssl=1 2048w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/chrome-devtools-waterfall-script-modules-default-priority.png?resize=150%2C40&amp;ssl=1 150w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<p>In my tests, both Chrome and Safari set a default fetch priority of <strong>high</strong> for module scripts and <code>modulepreload</code> links. In Firefox, the default fetch priority for a <code>modulepreload</code> link is <strong>highest</strong>, while the script modules are loaded with <strong>normal</strong> fetch priority. In all these cases, the priorities are <em>incorrect</em>. They should all have a fetch priority of <strong>low</strong> because they are not in the critical rendering path. This is because the very first requirement/goal defined for the Interactivity API was for <a href="https://developer.wordpress.org/block-editor/reference-guides/interactivity-api/iapi-about/#:~:text=It%20must%20support%20server%2Dside%20rendering.%20Server%2Drendered%20HTML%20and%20client%2Dhydrated%20HTML%20must%20be%20exactly%20the%20same.%20This%20is%20important%20for%20SEO%20and%20the%20user%20experience.">server-side rendering</a>:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>It must support server-side rendering. Server-rendered HTML and client-hydrated HTML must be exactly the same. This is important for SEO and the user experience.</p>
</blockquote>



<p>Since blocks using the Interactivity API are intended to leverage server-side rendering, the script modules for these blocks<sup data-fn="b77dfd89-ba79-47f9-b93a-b5825558c8a3" class="fn"><a id="b77dfd89-ba79-47f9-b93a-b5825558c8a3-link" href="#b77dfd89-ba79-47f9-b93a-b5825558c8a3">2</a></sup> by definition should not be prioritized over loading other resources which are in the critical rendering path, <em>especially</em> any image resource for the LCP element.</p>



<p>I wanted to find out what the LCP performance impact would be if these script modules had their priorities changed to low from the default of high. The <code><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLinkElement/fetchPriority">link</a></code> and <code><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLScriptElement/fetchPriority">script</a></code> tags both support the <code>fetchpriority</code> attribute, same as the <code>img</code> tag does. While WordPress now <a href="https://make.wordpress.org/core/2023/07/13/image-performance-enhancements-in-wordpress-6-3/">facilitates</a> adding <code>fetchpriority</code> to <code>img</code> tags, it doesn&#8217;t do the same for registered scripts or script modules. This is what <a title="Add the ability to handle &quot;fetchpriority&quot; to ES Modules and Import Maps" href="https://core.trac.wordpress.org/ticket/61734">#61734</a> is about, and it is why I&#8217;m analyzing the performance impact. In the same way that WordPress facilitates adding <code>async</code> and <code>defer</code> to scripts, and does so by default for some scripts, there should perhaps be a way to declare the <code>fetchpriority</code> for a script or script module.</p>



<h2 class="wp-block-heading" id="performance-of-low-priority-script-modules">Performance of Low Priority Script Modules</h2>



<p>In order to benchmark the performance impact, I developed the <strong><a href="https://github.com/westonruter/script-fetchpriority-low">Script Fetch Priority Low</a> plugin</strong> which automatically adds <code>fetchpriority=low</code> to the <code>module</code> scripts used by the Interactivity API, as well as any <code>modulepreload</code> links which are printed for static import dependencies. If a page is loaded with a specific query parameter, then the plugin short-circuits; this allows for doing before/after benchmarks.</p>



<p>With this plugin active, this is the change to the network panel in Chrome DevTools:</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bce067696&quot;}" data-wp-interactive="core/image" data-wp-key="6975bce067696" class="wp-block-image aligncenter size-large wp-lightbox-container"><img data-recalc-dims="1" loading="lazy" decoding="async" width="501" height="700" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 501px) 100vw, 501px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/chrome-network-panel-script-modules-low-priority-two-columns.png?resize=501%2C700&#038;ssl=1" alt="Screenshot of the Chrome DevTools network panel where the script module resources are being loaded with low priority after the Bison image (for the LCP element)" class="wp-image-34759" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/chrome-network-panel-script-modules-low-priority-two-columns.png?resize=501%2C700&amp;ssl=1 501w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/chrome-network-panel-script-modules-low-priority-two-columns.png?resize=215%2C300&amp;ssl=1 215w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/chrome-network-panel-script-modules-low-priority-two-columns.png?resize=768%2C1072&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/chrome-network-panel-script-modules-low-priority-two-columns.png?resize=1100%2C1536&amp;ssl=1 1100w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/chrome-network-panel-script-modules-low-priority-two-columns.png?resize=1467%2C2048&amp;ssl=1 1467w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/chrome-network-panel-script-modules-low-priority-two-columns.png?resize=150%2C209&amp;ssl=1 150w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/chrome-network-panel-script-modules-low-priority-two-columns.png?w=1510&amp;ssl=1 1510w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<p>Notice how the script modules are now being loaded with a <strong>low</strong> priority and <em>after</em> the Bison image. This can also be seen in the Performance panel waterfall:</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bce067f8e&quot;}" data-wp-interactive="core/image" data-wp-key="6975bce067f8e" class="wp-block-image alignwide size-large wp-lightbox-container"><img data-recalc-dims="1" loading="lazy" decoding="async" width="700" height="190" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 1340px) 100vw, 1340px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/chrome-devtools-waterfall-script-modules-low-priority.png?resize=700%2C190&#038;ssl=1" alt="Network waterfall in Chrome DevTools showing script modules loading after the LCP image resource." class="wp-image-34740" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/chrome-devtools-waterfall-script-modules-low-priority.png?resize=700%2C190&amp;ssl=1 700w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/chrome-devtools-waterfall-script-modules-low-priority.png?resize=300%2C81&amp;ssl=1 300w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/chrome-devtools-waterfall-script-modules-low-priority.png?resize=768%2C209&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/chrome-devtools-waterfall-script-modules-low-priority.png?resize=1536%2C417&amp;ssl=1 1536w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/chrome-devtools-waterfall-script-modules-low-priority.png?resize=2048%2C556&amp;ssl=1 2048w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/chrome-devtools-waterfall-script-modules-low-priority.png?resize=150%2C41&amp;ssl=1 150w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<p>This looks much better. But what is the performance impact in terms of LCP? To analyze that I used the <a href="https://github.com/GoogleChromeLabs/wpp-research/tree/main/cli#benchmark-web-vitals">benchmark-web-vitals</a> tool to obtain the median web vitals metrics for 100 requests with and without the reduction in fetch priority:</p>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>Command</summary><pre class="wp-block-code"><span><code class="hljs language-bash">npm run research -- benchmark-web-vitals --number=100 --output=<span class="hljs-string">"md"</span> --network-conditions=<span class="hljs-string">"broadband"</span> --diff \
  --url <span class="hljs-string">"http://localhost:10008/bison/?disable_print_script_modules_in_footer&amp;disable_script_fetchpriority_low"</span> \
  --url <span class="hljs-string">"http://localhost:10008/bison/?disable_print_script_modules_in_footer"</span></code></span></pre></details>



<figure class="wp-block-table benchmark-web-vitals is-style-regular has-medium-font-size"><table class="has-fixed-layout"><thead><tr><th>Metric</th><th>Before</th><th>After</th><th>Diff (ms)</th><th>Diff (%)</th></tr></thead><tbody><tr><td><abbr title="First Contentful Paint">FCP</abbr></td><td>142.6</td><td>141.7</td><td>-0.9</td><td>-0.6%</td></tr><tr><td><abbr title="Largest Contentful Paint">LCP</abbr></td><td>409.4</td><td>382.4</td><td>-27.0</td><td>-6.6%</td></tr><tr><td><abbr title="Time To Fist Byte">TTFB</abbr></td><td>34.7</td><td>35.3</td><td>+0.7</td><td>+1.9%</td></tr><tr><td><abbr title="LCP minus TTFB">LCP-TTFB</abbr></td><td>374.2</td><td>347.0</td><td>-27.2</td><td><mark style="background-color:#FFEE58" class="has-inline-color">-7.3%</mark></td></tr></tbody></table></figure>



<p>This is a big improvement! (Each subsequent benchmark test shows the median metrics of 100 iterations, unless otherwise noted.)</p>



<p>The above results were testing while emulating a broadband network connection. Here are the results testing a “Fast 3G” connection:</p>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>Command</summary><pre class="wp-block-code"><span><code class="hljs language-bash">npm run research -- benchmark-web-vitals --number=100 --output=<span class="hljs-string">"md"</span> --network-conditions=<span class="hljs-string">"Fast 3G"</span> --diff \
  --url <span class="hljs-string">"http://localhost:10008/bison/?disable_print_script_modules_in_footer&amp;disable_script_fetchpriority_low"</span> \
  --url <span class="hljs-string">"http://localhost:10008/bison/?disable_print_script_modules_in_footer"</span></code></span></pre></details>



<figure class="wp-block-table benchmark-web-vitals is-style-regular has-medium-font-size"><table class="has-fixed-layout"><thead><tr><th>Metric</th><th>Before</th><th>After</th><th>Diff (ms)</th><th>Diff (%)</th></tr></thead><tbody><tr><td>FCP</td><td>1275.3</td><td>1275.4</td><td>+0.1</td><td>+0.0%</td></tr><tr><td>LCP</td><td>3377.1</td><td>3157.1</td><td>-220.0</td><td>-6.5%</td></tr><tr><td>TTFB</td><td>35.0</td><td>34.6</td><td>-0.4</td><td>-1.0%</td></tr><tr><td>LCP-TTFB</td><td>3341.1</td><td>3123.0</td><td>-218.1</td><td><mark style="background-color:#FFEE58" class="has-inline-color">-6.5%</mark></td></tr></tbody></table></figure>



<p>So there is roughly the same improvement regardless of the network conditions.</p>



<p>I was also curious what the results would be when there was just a single block on the page using the Interactivity API, instead of attempting to add every single interactive block. This is the normal case for all block themes since the Navigation block is almost always present with the mobile overlay menu enabled. So I tested on a template with the Navigation block and a featured image as the LCP element, this time again emulating a broadband network connection:</p>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>Command</summary><pre class="wp-block-code"><span><code class="hljs language-bash">npm run research -- benchmark-web-vitals --number=100 --output=<span class="hljs-string">"md"</span> --network-conditions=<span class="hljs-string">"broadband"</span> --diff \
  --url <span class="hljs-string">"http://localhost:10008/bison-no-file-block/?disable_print_script_modules_in_footer&amp;disable_script_fetchpriority_low"</span> \
  --url <span class="hljs-string">"http://localhost:10008/bison-no-file-block/?disable_print_script_modules_in_footer"</span></code></span></pre></details>



<figure class="wp-block-table benchmark-web-vitals has-medium-font-size"><table class="has-fixed-layout"><thead><tr><th>Metric</th><th>Before</th><th>After</th><th>Diff (ms)</th><th>Diff (%)</th></tr></thead><tbody><tr><td>FCP</td><td>140.7</td><td>142.6</td><td>+1.9</td><td>+1.4%</td></tr><tr><td>LCP</td><td>399.0</td><td>368.9</td><td>-30.1</td><td>-7.5%</td></tr><tr><td>TTFB</td><td>33.4</td><td>33.0</td><td>-0.4</td><td>-1.2%</td></tr><tr><td>LCP-TTFB</td><td>365.1</td><td>335.8</td><td>-29.3</td><td><mark style="background-color:#FFEE58" class="has-inline-color">-8.0%</mark></td></tr></tbody></table></figure>



<p>So even when there is just a single script module along with the <code>modulepreload</code> link, the performance improvement to LCP is consistent.</p>



<h2 class="wp-block-heading" id="printing-script-modules-in-the-footer">Printing Script Modules in the Footer</h2>



<p>As referred to above, the classic method WordPress has employed to deprioritize scripts is to load them in the footer by supplying <code>in_footer</code> as <code>true</code> when registering a script. What if instead of adding a <code>fetchpriority</code> of <code>low</code> to script modules, they were instead just printed in the footer for block themes in the same way they are already printed in the footer for classic themes? I created the <strong><a href="https://github.com/westonruter/script-modules-in-footer">Script Modules in Footer</a> plugin</strong> to implement this and to facilitate benchmarking. Here are the results:</p>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>Command</summary><pre class="wp-block-code"><span><code class="hljs language-bash">npm run research -- benchmark-web-vitals --number=100 --output=<span class="hljs-string">"md"</span> --network-conditions=<span class="hljs-string">"broadband"</span> --diff \
  --url <span class="hljs-string">"http://localhost:10008/bison/?disable_print_script_modules_in_footer&amp;disable_script_fetchpriority_low"</span> \
  --url <span class="hljs-string">"http://localhost:10008/bison/?disable_script_fetchpriority_low"</span></code></span></pre></details>



<figure class="wp-block-table benchmark-web-vitals has-medium-font-size"><table class="has-fixed-layout"><thead><tr><th>Metric</th><th>Before</th><th>After</th><th>Diff (ms)</th><th>Diff (%)</th></tr></thead><tbody><tr><td>FCP</td><td>141.8</td><td>143.7</td><td>+1.9</td><td>+1.3%</td></tr><tr><td>LCP</td><td>410.8</td><td>383.1</td><td>-27.8</td><td>-6.8%</td></tr><tr><td>TTFB</td><td>34.4</td><td>34.4</td><td>-0.1</td><td>-0.1%</td></tr><tr><td>LCP-TTFB</td><td>376.7</td><td>348.4</td><td>-28.4</td><td><mark style="background-color:#FFEE58" class="has-inline-color">-7.5%</mark></td></tr></tbody></table></figure>



<p>This shows almost the same improvement as adding <code>fetchpriority</code> of <code>low</code>. However, even when they are located at the end of the <code>body</code>, they are still loaded with a high fetch priority. When script modules are printed in the footer, adding <code>fetchpriority</code> as well yields an additional ~1% improvement to LCP on my test page:</p>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>Command</summary><pre class="wp-block-code"><span><code class="hljs language-bash">npm run research -- benchmark-web-vitals --number=100 --output=<span class="hljs-string">"md"</span> --network-conditions=<span class="hljs-string">"broadband"</span> --diff \
  --url <span class="hljs-string">"http://localhost:10008/bison/?disable_script_fetchpriority_low"</span> \
  --url <span class="hljs-string">"http://localhost:10008/bison/"</span></code></span></pre></details>



<figure class="wp-block-table benchmark-web-vitals has-medium-font-size"><table class="has-fixed-layout"><thead><tr><th>Metric</th><th>Before</th><th>After</th><th>Diff (ms)</th><th>Diff (%)</th></tr></thead><tbody><tr><td>FCP</td><td>142.0</td><td>144.1</td><td>+2.1</td><td>+1.5%</td></tr><tr><td>LCP</td><td>377.5</td><td>374.7</td><td>-2.8</td><td>-0.7%</td></tr><tr><td>TTFB</td><td>34.7</td><td>35.1</td><td>+0.4</td><td>+1.2%</td></tr><tr><td>LCP-TTFB</td><td>342.9</td><td>338.9</td><td>-4.0</td><td>-1.2%</td></tr></tbody></table></figure>



<p>If script modules are all printed with <code>fetchpriority=low</code>, here&#8217;s the difference when moving them from <code>wp_head</code> to <code>wp_footer</code>:</p>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>Command</summary><pre class="wp-block-code"><span><code class="hljs language-bash">npm run research -- benchmark-web-vitals --number=100 --output=<span class="hljs-string">"md"</span> --network-conditions=<span class="hljs-string">"broadband"</span> --diff \
  --url <span class="hljs-string">"http://localhost:10008/bison/?disable_print_script_modules_in_footer"</span> \
  --url <span class="hljs-string">"http://localhost:10008/bison/"</span></code></span></pre></details>



<figure class="wp-block-table benchmark-web-vitals has-medium-font-size"><table class="has-fixed-layout"><thead><tr><th>Metric</th><th>Before</th><th>After</th><th>Diff (ms)</th><th>Diff (%)</th></tr></thead><tbody><tr><td>FCP</td><td>142.4</td><td>143.2</td><td>+0.8</td><td>+0.6%</td></tr><tr><td>LCP</td><td>383.9</td><td>372.7</td><td>-11.2</td><td>-2.9%</td></tr><tr><td>TTFB</td><td>35.0</td><td>35.2</td><td>+0.2</td><td>+0.6%</td></tr><tr><td>LCP-TTFB</td><td>348.1</td><td>337.8</td><td>-10.3</td><td><mark style="background-color:#FFEE58" class="has-inline-color">-3.0%</mark></td></tr></tbody></table></figure>



<p>So again, there&#8217;s an improvement but not as large as before/after adding <code>fetchpriority=low</code> or printing in the <code>head</code> versus the footer.</p>



<h2 class="wp-block-heading" id="deprioritizing-classic-scripts">Bonus: Deprioritizing Classic Scripts</h2>



<p>While classic scripts registered in WordPress already have the ability to be printed in the footer, they can&#8217;t be registered with a specific <code>fetchpriority</code> value. It doesn&#8217;t make a lot of sense to set a fetch priority for blocking classic scripts since they should always be loaded with the highest priority since they block rendering. However, what about a script that is using the <code>defer</code> or <code>async</code> loading strategies? Chrome automatically assigns such scripts as having a low priority, regardless of whether they are printed at the <code>head</code> or the footer. However, this is not the case for other browsers:</p>



<ul class="wp-block-list">
<li>An <code>async</code> script in the <code>head</code> gets a medium/normal priority in Safari/Firefox.</li>



<li> A <code>defer</code> script in the <code>head</code> gets a high priority in Safari and a normal priority in Firefox.</li>



<li>An <code>async</code> script in the footer gets a medium/normal priority in Safari/Firefox.</li>



<li>A <code>defer</code> script in the footer gets a high priority in Safari and a normal priority in Firefox.</li>
</ul>



<p>So for the sake of non-Chromium browsers, it absolutely makes sense to be able to register classic scripts with a fetch priority. For example, the <code>comment-reply</code> script is registered as <code>async</code> and is printed in the footer. Explicitly marking this script as having a low fetch priority ensures that loading it will not compete with more critical resources in Firefox and Safari. My <strong><a href="https://github.com/westonruter/script-fetchpriority-low">Script Fetch Priority Low</a></strong> plugin also implements this.</p>



<p>As an extra bonus, check out my <a href="https://github.com/westonruter/google-site-kit-gtag-script-deprioritization">Site Kit GTag Script Deprioritization</a> and <a href="https://github.com/westonruter/jetpack-stats-script-deprioritization">Jetpack Stats Script Deprioritization</a> plugins which optimize the loading of analytics trackers by adding <code>fetchpriority=low</code> to the <code>script</code> tags, removing the <code>dns-prefetch</code>, and ensuring the external <code>script</code> tags are printed in the footer.</p>



<h2 class="wp-block-heading" id="conclusion">Conclusion</h2>



<p>There are essentially for different configurations for printing script modules on the page:</p>



<ol class="wp-block-list">
<li>In <code>head</code> with default fetch priority. (This is the current behavior in WordPress core.)</li>



<li>In <code>head</code> with <code>fetchpriority=low</code>.</li>



<li>At end of <code>body</code> with default fetch priority.</li>



<li>At end of <code>body</code> with <code>fetchpriority=low</code>.</li>
</ol>



<p>Here are the median metrics for benchmarking 1,000 iterations for each of the four configurations:</p>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>Command</summary><pre class="wp-block-code"><span><code class="hljs language-bash">npm run research -- benchmark-web-vitals --number=1000 --output=<span class="hljs-string">"md"</span> --network-conditions=<span class="hljs-string">"broadband"</span> \
  --url <span class="hljs-string">"http://localhost:10008/bison/?disable_print_script_modules_in_footer&amp;disable_script_fetchpriority_low"</span> \
  --url <span class="hljs-string">"http://localhost:10008/bison/?disable_print_script_modules_in_footer"</span> \
  --url <span class="hljs-string">"http://localhost:10008/bison/?disable_script_fetchpriority_low"</span> \
  --url <span class="hljs-string">"http://localhost:10008/bison/"</span></code></span></pre></details>



<figure class="wp-block-table benchmark-web-vitals has-medium-font-size"><table class="has-fixed-layout"><thead><tr><th>Metric</th><th><code>head</code></th><th><code>head</code> + <code>low</code></th><th><code>body</code></th><th><code>body</code> + <code>low</code></th></tr></thead><tbody><tr><td>FCP</td><td>137.9</td><td>143.0</td><td>140.7</td><td>138.8</td></tr><tr><td>LCP</td><td>405.9</td><td>383.7</td><td>376.8</td><td>370.0</td></tr><tr><td>TTFB</td><td>33.7</td><td>34.5</td><td>33.6</td><td>33.7</td></tr><tr><td>LCP-TTFB</td><td>372.2</td><td><mark style="background-color:#FFEE58" class="has-inline-color">349.3</mark></td><td><mark style="background-color:#FFEE58" class="has-inline-color">343.0</mark></td><td><mark style="background-color:#FFEE58" class="has-inline-color">336.3</mark></td></tr></tbody></table></figure>



<p>When script modules are printed in the footer <em>and</em> they have <code>fetchpriority=low</code>, the result is a &gt;9% improvement to LCP on my test page:</p>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>Command</summary><pre class="wp-block-code"><span><code class="hljs language-bash">npm run research -- benchmark-web-vitals --number=100 --output=<span class="hljs-string">"md"</span> --network-conditions=<span class="hljs-string">"broadband"</span> --diff \
  --url <span class="hljs-string">"http://localhost:10008/bison/?disable_print_script_modules_in_footer&amp;disable_script_fetchpriority_low"</span> \
  --url <span class="hljs-string">"http://localhost:10008/bison/"</span></code></span></pre></details>



<figure class="wp-block-table benchmark-web-vitals has-medium-font-size"><table class="has-fixed-layout"><thead><tr><th>Metric</th><th>Before</th><th>After</th><th>Diff (ms)</th><th>Diff (%)</th></tr></thead><tbody><tr><td>FCP</td><td>137.0</td><td>137.2</td><td>+0.2</td><td>+0.1%</td></tr><tr><td>LCP</td><td>406.0</td><td>368.8</td><td>-37.2</td><td>-9.2%</td></tr><tr><td>TTFB</td><td>33.7</td><td>33.6</td><td>-0.1</td><td>-0.1%</td></tr><tr><td>LCP-TTFB</td><td>371.7</td><td>336.0</td><td>-35.7</td><td><mark style="background-color:#FFEE58" class="has-inline-color">-9.6%</mark></td></tr></tbody></table></figure>



<p>Of course your mileage will vary. This will primarily benefit <strong>block themes</strong>. A vanilla WordPress install with the stock theme is very lightweight and a typical site will have a lot more going on which will lessen the impact of these optimizations. But still, with these findings I&#8217;ve been working on a <a href="https://github.com/WordPress/wordpress-develop/pull/8815">pull request</a> for <a title="Add the ability to handle &quot;fetchpriority&quot; to ES Modules and Import Maps" href="https://core.trac.wordpress.org/ticket/61734">#61734</a> to implement <code>fetchpriority</code> support for scripts and script modules in WordPress core; it defaults the fetch priority to low for script modules related to the Interactivity API as well as the <code>comment-reply</code> classic script. This feature is a natural progression to follow script loading strategies (<code>async</code> &amp; <code>defer</code>); in fact, we could consider defaulting scripts to add <code>fetchpriority=low</code> if they use the a delayed loading strategy. </p>



<p>I&#8217;ve filed <a title="Script modules should support being printed in the footer the same as classic scripts" href="https://core.trac.wordpress.org/ticket/63486">#63486</a> to implement support for printing script modules in the footer. This work can follow the <code>fetchpriority</code> support as it will be more involved due to the need to account for module dependencies.</p>



<p>While waiting for these performance enhancements to land in core, you can install the <strong><a href="https://github.com/westonruter/script-fetchpriority-low">Script Fetch Priority Low</a></strong> and <strong><a href="https://github.com/westonruter/script-modules-in-footer">Script Modules in Footer</a></strong> plugins. Let me know if you measure any LCP improvements!</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="has-medium-font-size">Where I&#8217;ve shared this:</p>



<ul class="wp-block-social-links is-layout-flex wp-block-social-links-is-layout-flex"><li class="wp-social-link wp-social-link-linkedin  wp-block-social-link"><a href="https://www.linkedin.com/posts/westonruter_improve-lcp-by-deprioritizing-script-modules-activity-7332869961524092928-qv3g" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M19.7,3H4.3C3.582,3,3,3.582,3,4.3v15.4C3,20.418,3.582,21,4.3,21h15.4c0.718,0,1.3-0.582,1.3-1.3V4.3 C21,3.582,20.418,3,19.7,3z M8.339,18.338H5.667v-8.59h2.672V18.338z M7.004,8.574c-0.857,0-1.549-0.694-1.549-1.548 c0-0.855,0.691-1.548,1.549-1.548c0.854,0,1.547,0.694,1.547,1.548C8.551,7.881,7.858,8.574,7.004,8.574z M18.339,18.338h-2.669 v-4.177c0-0.996-0.017-2.278-1.387-2.278c-1.389,0-1.601,1.086-1.601,2.206v4.249h-2.667v-8.59h2.559v1.174h0.037 c0.356-0.675,1.227-1.387,2.526-1.387c2.703,0,3.203,1.779,3.203,4.092V18.338z"></path></svg><span class="wp-block-social-link-label screen-reader-text">LinkedIn</span></a></li>

<li class="wp-social-link wp-social-link-bluesky  wp-block-social-link"><a href="https://bsky.app/profile/weston.ruter.net/post/3lq42d67nxc2b" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M6.3,4.2c2.3,1.7,4.8,5.3,5.7,7.2.9-1.9,3.4-5.4,5.7-7.2,1.7-1.3,4.3-2.2,4.3.9s-.4,5.2-.6,5.9c-.7,2.6-3.3,3.2-5.6,2.8,4,.7,5.1,3,2.9,5.3-5,5.2-6.7-2.8-6.7-2.8,0,0-1.7,8-6.7,2.8-2.2-2.3-1.2-4.6,2.9-5.3-2.3.4-4.9-.3-5.6-2.8-.2-.7-.6-5.3-.6-5.9,0-3.1,2.7-2.1,4.3-.9h0Z"></path></svg><span class="wp-block-social-link-label screen-reader-text">Bluesky</span></a></li>

<li class="wp-social-link wp-social-link-twitter  wp-block-social-link"><a href="https://x.com/westonruter/status/1927104556170662353" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M22.23,5.924c-0.736,0.326-1.527,0.547-2.357,0.646c0.847-0.508,1.498-1.312,1.804-2.27 c-0.793,0.47-1.671,0.812-2.606,0.996C18.324,4.498,17.257,4,16.077,4c-2.266,0-4.103,1.837-4.103,4.103 c0,0.322,0.036,0.635,0.106,0.935C8.67,8.867,5.647,7.234,3.623,4.751C3.27,5.357,3.067,6.062,3.067,6.814 c0,1.424,0.724,2.679,1.825,3.415c-0.673-0.021-1.305-0.206-1.859-0.513c0,0.017,0,0.034,0,0.052c0,1.988,1.414,3.647,3.292,4.023 c-0.344,0.094-0.707,0.144-1.081,0.144c-0.264,0-0.521-0.026-0.772-0.074c0.522,1.63,2.038,2.816,3.833,2.85 c-1.404,1.1-3.174,1.756-5.096,1.756c-0.331,0-0.658-0.019-0.979-0.057c1.816,1.164,3.973,1.843,6.29,1.843 c7.547,0,11.675-6.252,11.675-11.675c0-0.178-0.004-0.355-0.012-0.531C20.985,7.47,21.68,6.747,22.23,5.924z"></path></svg><span class="wp-block-social-link-label screen-reader-text">Twitter</span></a></li>

<li class="wp-social-link wp-social-link-mastodon  wp-block-social-link"><a href="https://mastodon.social/@westonruter/114576094434226318" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M23.193 7.879c0-5.206-3.411-6.732-3.411-6.732C18.062.357 15.108.025 12.041 0h-.076c-3.068.025-6.02.357-7.74 1.147 0 0-3.411 1.526-3.411 6.732 0 1.192-.023 2.618.015 4.129.124 5.092.934 10.109 5.641 11.355 2.17.574 4.034.695 5.535.612 2.722-.15 4.25-.972 4.25-.972l-.09-1.975s-1.945.613-4.129.539c-2.165-.074-4.449-.233-4.799-2.891a5.499 5.499 0 0 1-.048-.745s2.125.52 4.817.643c1.646.075 3.19-.097 4.758-.283 3.007-.359 5.625-2.212 5.954-3.905.517-2.665.475-6.507.475-6.507zm-4.024 6.709h-2.497V8.469c0-1.29-.543-1.944-1.628-1.944-1.2 0-1.802.776-1.802 2.312v3.349h-2.483v-3.35c0-1.536-.602-2.312-1.802-2.312-1.085 0-1.628.655-1.628 1.944v6.119H4.832V8.284c0-1.289.328-2.313.987-3.07.68-.758 1.569-1.146 2.674-1.146 1.278 0 2.246.491 2.886 1.474L12 6.585l.622-1.043c.64-.983 1.608-1.474 2.886-1.474 1.104 0 1.994.388 2.674 1.146.658.757.986 1.781.986 3.07v6.304z"/></svg><span class="wp-block-social-link-label screen-reader-text">Mastodon</span></a></li></ul>



<p><strong>Shameless plug:</strong> <del datetime="2025-08-12">I found out last month that my 6½-year position at Google was eliminated. I was <a href="https://weston.ruter.net/2018/09/19/becoming-a-googler/">hired</a> to work on WordPress full time, and I&#8217;ve been <a href="https://weston.ruter.net/2025/05/14/a-decade-as-a-core-committer-my-wordpress-contribution-history/">contributing to WordPress</a> as a core committer for over 10 years. Most recently I&#8217;ve worked heavily on the Core Performance team. I&#8217;m currently #opentowork, hoping to find a full time position as a sponsored contributor at another company that also cares about the health of the open web. Alternatively, I&#8217;m exploring the feasibility of being <a href="https://github.com/sponsors/westonruter">sponsored</a> as an independent contributor. If you find my open source work valuable, maybe you can help sustain my contributions?</del></p>



<p><ins datetime="2025-08-12"><strong>Update:</strong>&nbsp;I’m no longer seeking additional sponsorship. (It’s good!)</ins></p>


<ol class="wp-block-footnotes has-small-font-size"><li id="c7a2be25-eace-4671-8089-f9286a5703ea">I used an Image block as opposed to setting the featured image so that I could use the “Enlarge on click” setting which involves the interactivity, but see also <a href="https://weston.ruter.net/2025/05/17/adding-caption-and-lightbox-to-the-featured-image-block/">my post</a> about how the Featured Image block can also be extended with a lightbox. <a href="#c7a2be25-eace-4671-8089-f9286a5703ea-link" aria-label="Jump to footnote reference 1">↩︎</a></li><li id="b77dfd89-ba79-47f9-b93a-b5825558c8a3">The one exception here is the <a href="https://wordpress.org/documentation/article/file-block/">File block</a> when a PDF is selected and the “Show inline embed” <a href="https://wordpress.org/documentation/article/file-block/#:~:text=If%20you%20upload%20a%20PDF%20file%2C%20you%E2%80%99ll%20see%20a%20PDF%20settings%20section%20on%20the%20block%20settings.">setting</a> is enabled. In this case, JavaScript runs when the block initializes to <a href="https://github.com/WordPress/gutenberg/blob/8889f82eda340ea66c83e945098423ed1ae3f5d3/packages/block-library/src/file/view.js#L13-L17">populate the <code>hasPdfPreview</code> state</a>, and this then changes the element&#8217;s <code>hidden</code> state from <code>true</code> to <code>false</code> if the browser supports rendering PDFs. This is an exceptional case, however, and it would be rare for a File block to appear in the initial viewport and be the LCP element. The script module for the File block should only have a high fetch priority if (1) a PDF is selected, (2) the “Show inline embed” setting is enabled, and (3) the block appears in the initial viewport (on desktop or mobile). This is something that <a href="https://github.com/WordPress/performance/blob/trunk/plugins/optimization-detective/docs/introduction.md">Optimization Detective</a> could facilitate. <a href="#b77dfd89-ba79-47f9-b93a-b5825558c8a3-link" aria-label="Jump to footnote reference 2">↩︎</a></li></ol><p>The post <a href="https://weston.ruter.net/2025/05/26/improve-lcp-by-deprioritizing-interactivity-api-script-modules/">Improve LCP by Deprioritizing  Script Modules from the Interactivity API</a> appeared first on <a href="https://weston.ruter.net">Weston Ruter</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://weston.ruter.net/2025/05/26/improve-lcp-by-deprioritizing-interactivity-api-script-modules/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">34457</post-id>	</item>
		<item>
		<title>Adding Caption &#038; Lightbox to the Featured Image Block</title>
		<link>https://weston.ruter.net/2025/05/17/adding-caption-and-lightbox-to-the-featured-image-block/</link>
					<comments>https://weston.ruter.net/2025/05/17/adding-caption-and-lightbox-to-the-featured-image-block/#comments</comments>
		
		<dc:creator><![CDATA[Weston Ruter]]></dc:creator>
		<pubDate>Sun, 18 May 2025 03:28:23 +0000</pubDate>
				<category><![CDATA[WordPress]]></category>
		<guid isPermaLink="false">https://weston.ruter.net/?p=34581</guid>

					<description><![CDATA[<p>Earlier this year I re-built this site using the Twenty Twenty-Five theme, finally adopting a block theme over the veritable Twenty Twenty classic theme I had been using. In this rebuild, one thing that I had trouble with is the featured image. In the Twenty Twenty theme, a caption is rendered under the featured image [&#8230;]</p>
<p>The post <a href="https://weston.ruter.net/2025/05/17/adding-caption-and-lightbox-to-the-featured-image-block/">Adding Caption &amp; Lightbox to the Featured Image Block</a> appeared first on <a href="https://weston.ruter.net">Weston Ruter</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Earlier this year I re-built this site using the <a href="https://wordpress.org/themes/twentytwentyfive/">Twenty Twenty-Five</a> theme, finally adopting a <a href="https://wordpress.org/documentation/article/block-themes/">block theme</a> over the veritable <a href="https://wordpress.org/themes/twentytwenty/">Twenty Twenty</a> classic theme I had been using. In this rebuild, one thing that I had trouble with is the featured image. In the Twenty Twenty theme, a <a href="https://github.com/WordPress/wordpress-develop/blob/8b466fce082b5b8731cebca85469ae1978318504/src/wp-content/themes/twentytwenty/template-parts/featured-image.php#L27-L36">caption is rendered</a> under the featured image if one is set on the underlying <code>attachment</code> post in the Media Library. In the Twenty Twenty-Five theme, however, no caption is shown by the <a href="https://wordpress.org/documentation/article/post-featured-image-block/">Featured Image block</a>:</p>



<div class="wp-block-columns is-not-stacked-on-mobile is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bce07f63f&quot;}" data-wp-interactive="core/image" data-wp-key="6975bce07f63f" class="wp-block-image size-large has-custom-border wp-lightbox-container"><img data-recalc-dims="1" loading="lazy" decoding="async" width="394" height="700" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 322px) 100vw, 322px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/twentytwenty-single-template.png?resize=394%2C700&#038;ssl=1" alt="Single template of Twenty Twenty theme showing an article about the Bison with an image of the Bison as the featured image. A caption appears below the Bison image." class="has-border-color has-accent-4-border-color wp-image-34604" style="border-width:1px" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/twentytwenty-single-template.png?resize=394%2C700&amp;ssl=1 394w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/twentytwenty-single-template.png?resize=169%2C300&amp;ssl=1 169w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/twentytwenty-single-template.png?resize=150%2C267&amp;ssl=1 150w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/twentytwenty-single-template.png?w=750&amp;ssl=1 750w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button><figcaption class="wp-element-caption">Caption shown with featured image in the Twenty Twenty theme.</figcaption></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bce07fdd9&quot;}" data-wp-interactive="core/image" data-wp-key="6975bce07fdd9" class="wp-block-image size-large has-custom-border wp-lightbox-container"><img data-recalc-dims="1" loading="lazy" decoding="async" width="394" height="700" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 322px) 100vw, 322px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/twentytwentyfive-single-template.png?resize=394%2C700&#038;ssl=1" alt="Single template of Twenty Twenty-Five theme showing an article about the Bison with an image of the Bison as the featured image. A caption does not appear below the Bison image." class="has-border-color has-accent-4-border-color wp-image-34605" style="border-width:1px" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/twentytwentyfive-single-template.png?resize=394%2C700&amp;ssl=1 394w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/twentytwentyfive-single-template.png?resize=169%2C300&amp;ssl=1 169w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/twentytwentyfive-single-template.png?resize=150%2C267&amp;ssl=1 150w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/twentytwentyfive-single-template.png?w=750&amp;ssl=1 750w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button><figcaption class="wp-element-caption">Caption for featured image omitted in the Twenty Twenty-Five theme.</figcaption></figure>
</div>
</div>



<p>I was expecting the Featured Image block to have an &#8220;Add caption&#8221;/“Remove caption” button in the block toolbar. This is available in the regular Image block, but it is absent in the Featured Image block:</p>



<div class="wp-block-columns is-not-stacked-on-mobile is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bce0807c2&quot;}" data-wp-interactive="core/image" data-wp-key="6975bce0807c2" class="wp-block-image size-large has-custom-border wp-lightbox-container"><img data-recalc-dims="1" loading="lazy" decoding="async" width="624" height="700" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 322px) 100vw, 322px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/twentytwentyfive-block-editor-image-block.png?resize=624%2C700&#038;ssl=1" alt="Block editor showing an Image block selected with the caption appearing below the image and the &quot;Remove Caption&quot; button being hovered over in the block toolbar." class="has-border-color has-accent-4-border-color wp-image-34606" style="border-width:1px" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/twentytwentyfive-block-editor-image-block.png?resize=624%2C700&amp;ssl=1 624w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/twentytwentyfive-block-editor-image-block.png?resize=267%2C300&amp;ssl=1 267w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/twentytwentyfive-block-editor-image-block.png?resize=768%2C862&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/twentytwentyfive-block-editor-image-block.png?resize=150%2C168&amp;ssl=1 150w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/twentytwentyfive-block-editor-image-block.png?w=1000&amp;ssl=1 1000w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button><figcaption class="wp-element-caption">Image block in the block editor with “Remove caption” button hovered.</figcaption></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bce080f47&quot;}" data-wp-interactive="core/image" data-wp-key="6975bce080f47" class="wp-block-image size-large has-custom-border wp-lightbox-container"><img data-recalc-dims="1" loading="lazy" decoding="async" width="638" height="700" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 322px) 100vw, 322px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/twentytwentyfive-site-editor-featured-image-block.png?resize=638%2C700&#038;ssl=1" alt="Site editor showing a Featured Image block selected with there not being any caption displayed and no block toolbar button for captions." class="has-border-color has-accent-4-border-color wp-image-34607" style="border-width:1px" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/twentytwentyfive-site-editor-featured-image-block.png?resize=638%2C700&amp;ssl=1 638w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/twentytwentyfive-site-editor-featured-image-block.png?resize=273%2C300&amp;ssl=1 273w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/twentytwentyfive-site-editor-featured-image-block.png?resize=768%2C843&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/twentytwentyfive-site-editor-featured-image-block.png?resize=150%2C165&amp;ssl=1 150w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/twentytwentyfive-site-editor-featured-image-block.png?w=1002&amp;ssl=1 1002w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button><figcaption class="wp-element-caption">Featured Image block without any “Add caption” button shown in the block toolbar.</figcaption></figure>
</div>
</div>



<p>It turns out there is a 3-year old Gutenberg issue (<a title="[Post Featured Image]: add option to display image caption/credit" href="https://github.com/WordPress/gutenberg/issues/40946">#40946</a>) about this missing capability in the Featured Image block. But this isn&#8217;t the only missing feature from the block. In addition to the caption, something else I was missing is the ability to open the featured image in a lightbox, something which the Image block exposes by selecting the link option to “Enlarge on click”. The Featured Image block does not have this Link button in the block toolbar but instead just has a “Link to Post” setting toggle in the block sidebar:</p>



<div class="wp-block-columns is-not-stacked-on-mobile is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:36.2%">
<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bce0818d3&quot;}" data-wp-interactive="core/image" data-wp-key="6975bce0818d3" class="wp-block-image size-large has-custom-border wp-lightbox-container"><img data-recalc-dims="1" loading="lazy" decoding="async" width="625" height="700" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 233px) 100vw, 233px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/image-block-enlarge-on-click.png?resize=625%2C700&#038;ssl=1" alt="Block editor with the Link block toolbar button having been clicked, showing the Link options to &quot;Link to image file&quot;, &quot;Link to attachment page&quot;, and &quot;Enlarge on click&quot;. The last option is focused." class="has-border-color has-accent-4-border-color wp-image-34608" style="border-width:1px" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/image-block-enlarge-on-click.png?resize=625%2C700&amp;ssl=1 625w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/image-block-enlarge-on-click.png?resize=268%2C300&amp;ssl=1 268w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/image-block-enlarge-on-click.png?resize=768%2C860&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/image-block-enlarge-on-click.png?resize=150%2C168&amp;ssl=1 150w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/image-block-enlarge-on-click.png?w=1000&amp;ssl=1 1000w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button><figcaption class="wp-element-caption">Link submenu showing “Enlarge on click” option for Image block.</figcaption></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bce0820ca&quot;}" data-wp-interactive="core/image" data-wp-key="6975bce0820ca" class="wp-block-image size-large has-custom-border wp-lightbox-container"><img data-recalc-dims="1" loading="lazy" decoding="async" width="700" height="500" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 322px) 100vw, 322px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/featured-image-block-block-sidebar.png?resize=700%2C500&#038;ssl=1" alt="Site editor showing showing the Featured Image block selected, bit without there being a Link button in the block toolbar. The block sidebar is expanded to show a single setting available: &quot;Link to Post&quot;." class="has-border-color has-accent-4-border-color wp-image-34609" style="border-width:1px" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/featured-image-block-block-sidebar.png?resize=700%2C500&amp;ssl=1 700w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/featured-image-block-block-sidebar.png?resize=300%2C214&amp;ssl=1 300w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/featured-image-block-block-sidebar.png?resize=768%2C549&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/featured-image-block-block-sidebar.png?resize=1536%2C1097&amp;ssl=1 1536w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/featured-image-block-block-sidebar.png?resize=150%2C107&amp;ssl=1 150w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/featured-image-block-block-sidebar.png?w=1568&amp;ssl=1 1568w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button><figcaption class="wp-element-caption">Block toolbar missing Link button but showing “Link to Post” setting in the block sidebar for the Featured Image block.</figcaption></figure>
</div>
</div>



<p>There is also an open Gutenberg issue to add this lightbox capability to the Featured Image block (<a title="Featured Image block: Lightbox / Expand use case " href="https://github.com/WordPress/gutenberg/issues/57849">#57849</a>).</p>



<p>Because I want these features now before they get implemented in Gutenberg, I&#8217;ve put together a couple plugins (on GitHub) that extend the Featured Image block with these missing capabilities:</p>



<ul class="wp-block-list">
<li><a href="https://github.com/westonruter/featured-image-block-with-caption">Featured Image Block with Caption</a></li>



<li><a href="https://github.com/westonruter/featured-image-block-with-lightbox">Featured Image Block with Lightbox</a></li>
</ul>



<p>These are active on this site, so you can see above how the featured image is enhanced with a caption and lightbox.</p>



<p>While the Gutenberg issue for adding a caption has a <a href="https://github.com/WordPress/gutenberg/issues/40946#issuecomment-1682973710">workaround</a> involving the registration of a new block variation, I wanted there to be the same &#8220;Add caption&#8221; block toolbar button which is available on the Image block, and I wanted to be able to see a placeholder for how the caption would look in the Site Editor. So this is what I implemented:</p>



<div class="wp-block-columns alignwide is-not-stacked-on-mobile is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bce082e10&quot;}" data-wp-interactive="core/image" data-wp-key="6975bce082e10" class="wp-block-image size-large has-custom-border wp-lightbox-container"><img data-recalc-dims="1" loading="lazy" decoding="async" width="636" height="700" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 322px) 100vw, 322px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/featured-image-block-with-add-caption-button.png?resize=636%2C700&#038;ssl=1" alt="Site Editor showing the Featured Image block selected and a new &quot;Add caption&quot; button appearing in the toolbar." class="has-border-color has-accent-4-border-color wp-image-34613" style="border-width:1px" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/featured-image-block-with-add-caption-button.png?resize=636%2C700&amp;ssl=1 636w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/featured-image-block-with-add-caption-button.png?resize=273%2C300&amp;ssl=1 273w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/featured-image-block-with-add-caption-button.png?resize=768%2C845&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/featured-image-block-with-add-caption-button.png?resize=150%2C165&amp;ssl=1 150w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/featured-image-block-with-add-caption-button.png?w=1000&amp;ssl=1 1000w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button><figcaption class="wp-element-caption">Featured Image block in the Site Editor with an “Add caption” button appearing in the block toolbar. </figcaption></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure data-wp-context="{&quot;imageId&quot;:&quot;6975bce0835a6&quot;}" data-wp-interactive="core/image" data-wp-key="6975bce0835a6" class="wp-block-image size-large has-custom-border wp-lightbox-container"><img data-recalc-dims="1" loading="lazy" decoding="async" width="636" height="700" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" sizes="auto, (max-width: 322px) 100vw, 322px" src="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/featured-image-block-with-caption-placeholder-shown.png?resize=636%2C700&#038;ssl=1" alt="Site Editor showing the Featured Image block selected and the &quot;Add caption&quot; button being selected in the block toolbar. A placeholder caption appears below the image." class="has-border-color has-accent-4-border-color wp-image-34614" style="border-width:1px" srcset="https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/featured-image-block-with-caption-placeholder-shown.png?resize=636%2C700&amp;ssl=1 636w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/featured-image-block-with-caption-placeholder-shown.png?resize=273%2C300&amp;ssl=1 273w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/featured-image-block-with-caption-placeholder-shown.png?resize=768%2C845&amp;ssl=1 768w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/featured-image-block-with-caption-placeholder-shown.png?resize=150%2C165&amp;ssl=1 150w, https://i0.wp.com/weston.ruter.net/wp-content/uploads/2025/05/featured-image-block-with-caption-placeholder-shown.png?w=1000&amp;ssl=1 1000w" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button><figcaption class="wp-element-caption">Caption placeholder appearing in Featured Image block in the Site Editor.</figcaption></figure>
</div>
</div>



<p>Selecting “Add caption” sets a new <code>showCaption</code> block attribute to <code>true</code>, without making any changes to the block markup. This means there won&#8217;t be any block validation errors when the plugin is deactivated. When the block attribute is present, a <code>render_block</code> filter in PHP injects the caption into the block&#8217;s markup on the frontend. On my site, I edited the Single template to show the caption in the Featured Image block, but I left the caption off for the block on the Home template in order to keep the blog index clean. Editing the text of a Featured Image block&#8217;s caption is not done inline in the block editor as is done with the Image block; instead, editing the featured image caption requires either selecting the image in the Media Library in the post editor, opening the image in the <a href="https://wordpress.org/documentation/article/media-library-screen/#attachment-details">Media Library screen</a>, or opening it in the seldom-accessed <a href="https://wordpress.org/documentation/article/edit-media/">Edit Media screen</a>.</p>



<p>The plugin to implement a lightbox for the Featured Image block is much simpler, and there is no UI. There&#8217;s simply another <code>render_block</code> filter which checks to see if the “Link to Post” setting is enabled, and if not, then it enables the “enlarge on click” functionality. Normally the “Link to Post” setting is not enabled on the Single template, allowing the featured image to open in a lightbox the same as any other Image block which have lightbox enabled. The implementation was trivial because all it needed to do is enqueue the same script module and stylesheet as the Image block does, and then it passes the block content into the same <a href="https://github.com/WordPress/wordpress-develop/blob/6.8.1/src/wp-includes/blocks/image.php#L128-L248">function</a> used by the Image block. In the end, the Featured Image block&#8217;s markup looks very similar to the Image block&#8217;s markup:</p>


<pre class="wp-block-code alignwide has-small-font-size"><span><code class="hljs language-diff"><span class="hljs-comment">--- image-block.html</span>
<span class="hljs-comment">+++ featured-image-block.html</span>
<span class="hljs-meta">@@ -1,11 +1,12 @@</span>
 &lt;figure
 	data-wp-context='{"imageId":"68293cf73baf0"}'
 	data-wp-interactive="core/image"
<span class="hljs-deletion">-	class="wp-block-image size-full wp-lightbox-container"</span>
<span class="hljs-addition">+	style="aspect-ratio: auto"</span>
<span class="hljs-addition">+	class="wp-block-post-featured-image wp-lightbox-container"</span>
 &gt;
 	&lt;img
 		alt=""
<span class="hljs-deletion">-		class="wp-image-8"</span>
<span class="hljs-addition">+		class="attachment-post-thumbnail size-post-thumbnail wp-post-image"</span>
 		data-wp-class--hide="state.isContentHidden"
 		data-wp-class--show="state.isContentVisible"
 		data-wp-init="callbacks.setButtonStyles"
<span class="hljs-meta">@@ -25,6 +26,7 @@</span>
 			http://localhost:10013/wp-content/uploads/2025/05/American_bison_k5680-1-1536x1002.jpg 1536w,
 			http://localhost:10013/wp-content/uploads/2025/05/American_bison_k5680-1-2048x1336.jpg 2048w
 		"
<span class="hljs-addition">+		style="width: 100%; height: 100%; object-fit: cover"</span>
 		width="2560"
 	/&gt;&lt;button
 		class="lightbox-trigger"</code></span></pre>


<p>Ultimately, I <a href="https://github.com/WordPress/gutenberg/issues/57849#issuecomment-2704434044">think</a> the Featured Image block could be deprecated in favor of adding a “Use featured image” source to the Image block. This would allow all of its features to be re-used in a featured image context. I suppose “Link to Post” would have to be added as one of the Link options when the featured image is used as the source.</p>



<p>I hope this is helpful for you with building sites with block themes while waiting for these features to land in the core Featured Image block!</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>Where I&#8217;ve shared this:</p>



<ul class="wp-block-social-links is-layout-flex wp-block-social-links-is-layout-flex"><li class="wp-social-link wp-social-link-bluesky  wp-block-social-link"><a href="https://bsky.app/profile/weston.ruter.net/post/3lpg5cfpvj22d" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M6.3,4.2c2.3,1.7,4.8,5.3,5.7,7.2.9-1.9,3.4-5.4,5.7-7.2,1.7-1.3,4.3-2.2,4.3.9s-.4,5.2-.6,5.9c-.7,2.6-3.3,3.2-5.6,2.8,4,.7,5.1,3,2.9,5.3-5,5.2-6.7-2.8-6.7-2.8,0,0-1.7,8-6.7,2.8-2.2-2.3-1.2-4.6,2.9-5.3-2.3.4-4.9-.3-5.6-2.8-.2-.7-.6-5.3-.6-5.9,0-3.1,2.7-2.1,4.3-.9h0Z"></path></svg><span class="wp-block-social-link-label screen-reader-text">Bluesky</span></a></li>

<li class="wp-social-link wp-social-link-linkedin  wp-block-social-link"><a href="https://www.linkedin.com/posts/westonruter_adding-caption-lightbox-to-the-featured-activity-7329712341493698561-3GQE" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M19.7,3H4.3C3.582,3,3,3.582,3,4.3v15.4C3,20.418,3.582,21,4.3,21h15.4c0.718,0,1.3-0.582,1.3-1.3V4.3 C21,3.582,20.418,3,19.7,3z M8.339,18.338H5.667v-8.59h2.672V18.338z M7.004,8.574c-0.857,0-1.549-0.694-1.549-1.548 c0-0.855,0.691-1.548,1.549-1.548c0.854,0,1.547,0.694,1.547,1.548C8.551,7.881,7.858,8.574,7.004,8.574z M18.339,18.338h-2.669 v-4.177c0-0.996-0.017-2.278-1.387-2.278c-1.389,0-1.601,1.086-1.601,2.206v4.249h-2.667v-8.59h2.559v1.174h0.037 c0.356-0.675,1.227-1.387,2.526-1.387c2.703,0,3.203,1.779,3.203,4.092V18.338z"></path></svg><span class="wp-block-social-link-label screen-reader-text">LinkedIn</span></a></li>

<li class="wp-social-link wp-social-link-twitter  wp-block-social-link"><a href="https://x.com/westonruter/status/1923947578217071045" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M22.23,5.924c-0.736,0.326-1.527,0.547-2.357,0.646c0.847-0.508,1.498-1.312,1.804-2.27 c-0.793,0.47-1.671,0.812-2.606,0.996C18.324,4.498,17.257,4,16.077,4c-2.266,0-4.103,1.837-4.103,4.103 c0,0.322,0.036,0.635,0.106,0.935C8.67,8.867,5.647,7.234,3.623,4.751C3.27,5.357,3.067,6.062,3.067,6.814 c0,1.424,0.724,2.679,1.825,3.415c-0.673-0.021-1.305-0.206-1.859-0.513c0,0.017,0,0.034,0,0.052c0,1.988,1.414,3.647,3.292,4.023 c-0.344,0.094-0.707,0.144-1.081,0.144c-0.264,0-0.521-0.026-0.772-0.074c0.522,1.63,2.038,2.816,3.833,2.85 c-1.404,1.1-3.174,1.756-5.096,1.756c-0.331,0-0.658-0.019-0.979-0.057c1.816,1.164,3.973,1.843,6.29,1.843 c7.547,0,11.675-6.252,11.675-11.675c0-0.178-0.004-0.355-0.012-0.531C20.985,7.47,21.68,6.747,22.23,5.924z"></path></svg><span class="wp-block-social-link-label screen-reader-text">Twitter</span></a></li>

<li class="wp-social-link wp-social-link-mastodon  wp-block-social-link"><a href="https://mastodon.social/@westonruter/114526760610405491" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M23.193 7.879c0-5.206-3.411-6.732-3.411-6.732C18.062.357 15.108.025 12.041 0h-.076c-3.068.025-6.02.357-7.74 1.147 0 0-3.411 1.526-3.411 6.732 0 1.192-.023 2.618.015 4.129.124 5.092.934 10.109 5.641 11.355 2.17.574 4.034.695 5.535.612 2.722-.15 4.25-.972 4.25-.972l-.09-1.975s-1.945.613-4.129.539c-2.165-.074-4.449-.233-4.799-2.891a5.499 5.499 0 0 1-.048-.745s2.125.52 4.817.643c1.646.075 3.19-.097 4.758-.283 3.007-.359 5.625-2.212 5.954-3.905.517-2.665.475-6.507.475-6.507zm-4.024 6.709h-2.497V8.469c0-1.29-.543-1.944-1.628-1.944-1.2 0-1.802.776-1.802 2.312v3.349h-2.483v-3.35c0-1.536-.602-2.312-1.802-2.312-1.085 0-1.628.655-1.628 1.944v6.119H4.832V8.284c0-1.289.328-2.313.987-3.07.68-.758 1.569-1.146 2.674-1.146 1.278 0 2.246.491 2.886 1.474L12 6.585l.622-1.043c.64-.983 1.608-1.474 2.886-1.474 1.104 0 1.994.388 2.674 1.146.658.757.986 1.781.986 3.07v6.304z"/></svg><span class="wp-block-social-link-label screen-reader-text">Mastodon</span></a></li></ul>
<p>The post <a href="https://weston.ruter.net/2025/05/17/adding-caption-and-lightbox-to-the-featured-image-block/">Adding Caption &amp; Lightbox to the Featured Image Block</a> appeared first on <a href="https://weston.ruter.net">Weston Ruter</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://weston.ruter.net/2025/05/17/adding-caption-and-lightbox-to-the-featured-image-block/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">34581</post-id>	</item>
	</channel>
</rss>
