Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save westonruter/78c0640fb108e2e94e7b0a536e9a9140 to your computer and use it in GitHub Desktop.
Save westonruter/78c0640fb108e2e94e7b0a536e9a9140 to your computer and use it in GitHub Desktop.
<?php
/**
* Plugin Name: Speculative Loading: Eagerly Prerender First Post in Archive Loop
* Plugin URI: https://gist.github.com/westonruter/78c0640fb108e2e94e7b0a536e9a9140
* Description: For templates that list out one or more posts (i.e. blog index, archives, search results), eagerly prerender the first post in The Loop since it is the most likely link the user will visit.
* Requires at least: 5.7
* Requires PHP: 7.2
* Version: 0.1.0
* Author: Weston Ruter
* Author URI: https://weston.ruter.net/
* License: GPLv2 or later
* License URI: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* Update URI: https://gist.github.com/westonruter/78c0640fb108e2e94e7b0a536e9a9140
* Gist Plugin URI: https://gist.github.com/westonruter/78c0640fb108e2e94e7b0a536e9a9140
*
* @package SpeculationRulesPrerenderFirstPostInArchiveLoop
*/
add_action(
'wp',
static function (): void {
global $wp_query;
// Abort if WP is not serving a page which should prerender another.
if (
// Short-circuit if not on an archive-like template.
! (
is_archive() // Archives for categories, authors, taxonomies, dates, tags, and CPTs.
||
is_home() // The blog index page (either on the homepage or the page for posts if a static homepage is set).
||
is_search() // This may put too much faith in WordPress's search algorithm!
)
||
// This is perhaps overly defensive since if there is no post then surely it'd be is_404().
! isset( $wp_query->posts[0] )
) {
return;
}
$first_post = get_post( $wp_query->posts[0] );
if ( ! ( $first_post instanceof WP_Post ) ) {
// More defensive coding.
return;
}
/*
* Create the eager speculation rule for the first post's link as found in the template.
* This presumes that the HTML is constructed with post_class() and has headings in the loop which contain the
* post permalinks, which is the case for all core themes from Twenty Twenty-Five going back to Twenty Ten.
*/
$speculation_rule = array(
'source' => 'document',
'where' => array(
'selector_matches' => sprintf( '.hentry.post-%d :is(h1, h2, h3) a[href]', $first_post->ID ),
),
'eagerness' => 'eager',
);
/*
* As an alternative to the above, the speculation rule could instead be constructed as follows:
*
* array(
* 'source' => 'list',
* 'urls' => array( get_permalink( $wp_query->posts[0] ) ),
* 'eagerness' => 'eager',
* )
*
* However, there is a danger that a plugin may append query parameters to the links as they are rendered in
* the template. This could result in a post permalink being eagerly prerendered which does not correspond
* to the actual post link which the user navigates to, and thereby resulting in a wasted the prerender.
* An added benefit to using a document source with a selector is that it ensures the URL being prerendered
* actually appears in the page as a link: granted it surely would the vast majority of the time, but in
* WordPress anything is possible.
*/
// Add the new speculation rule to the page.
if ( class_exists( 'WP_Speculation_Rules' ) ) {
// This branch is for WordPress 6.8-beta1. See <https://make.wordpress.org/core/2025/03/06/speculative-loading-in-6-8/>.
add_action(
'wp_load_speculation_rules',
static function ( WP_Speculation_Rules $speculation_rules ) use ( $speculation_rule ): void {
$speculation_rules->add_rule(
'prerender',
'prerender-first-post-in-archive-loop',
$speculation_rule
);
}
);
} else {
// This branch is for WordPress versions prior to 6.8-beta1.
// Note that multiple 'speculationrules' scripts can be on a page, so it is fine if the Speculative Loading
// plugin is also adding its script that may do moderate prerendering for all links as well.
add_action(
'wp_footer',
static function () use ( $speculation_rule ): void {
wp_print_inline_script_tag(
(string) wp_json_encode( array( 'prerender' => array( $speculation_rule ) ) ),
array( 'type' => 'speculationrules' )
);
}
);
}
}
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment