Speculative Loading in WordPress 6.8

In WordPress 6.8, the speculative loading feature is integrated into the core. This innovation can provide almost instant page loading due to preloading by the browser before the user clicks on a link.

The speculative loading feature was successfully used on over 50,000 sites through the Speculative Loading plugin before its implementation in WordPress Core. According to HTTP Archive and CrUX, it improved LCP by ~2%.

The Speculation Rules API appeared in 2023 and is already used in 8% of navigations in Chrome. Cloudflare has also implemented it through its Speed Brain feature.

What is speculative loading?

Speculative loading — a web API from Google that allows setting rules for which URLs on the current page can be preloaded/rendered and when such preloading should occur.

Goal — to speed up transitions between pages and improve performance metrics (e.g., LCP).

The word "speculative" means "assumed," "based on a guess." It comes from the Latin speculatio — observation, reflection, prediction.

In the context of "speculative loading," it is used because the browser does not know for sure whether the user will click on the link but assumes so and preloads the resource in advance.

Which pages are suitable for speculative loading?

  • Any pages not modified by the user. A good rule is to avoid preloading checkout and cart pages. Google also recommends doing prerendering only for pages that the user is likely to open with more than 80% probability; if unsure, use prefetch.

How does it work?

  • The developer sets rules: which URLs are suitable and how they should be loaded (prefetch or prerender).
  • The browser uses these rules to preload content without waiting for a click.

Initialization (defining rules)

Developers can set rules in JSON format, specifying which URLs (pages) to preload or render.

These rules can be:

  • Passed through the HTTP header Speculation-Rules:

    Speculation-Rules: "/rules/prefetch.json","/rules/prerender.json"
  • Embedded in the HTML document inside a script tag:

    <script type="speculationrules">
    {
      "prerender": [
    	{
    	  "source": "document",
    	  "where": {
    		"and": [
    		  {
    			"href_matches": "/*"
    		  },
    		  {
    			"not": {
    			  "href_matches": [
    				"/wp-login.php",
    				"/wp-admin/*"
    			  ]
    			}
    		  }
    		]
    	  },
    	  "eagerness": "moderate"
    	}
      ]
    }
    </script>

Parameters (options)

Main parameters

  • Loading type — The API supports two types of preloading:

    • prefetch — loads only the HTML page without subresources. This speeds up loading during transitions.

    • prerender — loads and renders the entire page in advance (including JS and styles) in a hidden tab, ensuring almost instant opening.

      But prerender consumes more memory and traffic, so it should be used with caution — resources may be wasted if the user does not navigate to the page.

  • eagerness ― Level of "impatience" — determines when the browser should start preloading:
    • conservative — loads when the link is clicked (pointerdown or touch).
    • moderate — loads when the cursor hovers over the link for 200 ms (or on pointerdown if earlier, especially relevant for mobile devices where there is no hover).
    • eager — loads immediately after the link appears on the page.
    • immediate — loads immediately after the speculative loading rules are detected. Not used in WordPress.

All parameters

General structure:

{
  "prerender": [ /* rules */ ],
  "prefetch": [ /* rules */ ]
}

Each of the fields (prerender, prefetch) contains an array of rule objects, which specify the parameters below:

Key Type Description
source string Required. Where to get URLs from: list or document
urls string[] Required if source: list. List of URLs or paths
where object Required if source: document. Defines which elements are involved
eagerness string Impatience: conservative, moderate, eager, immediate
requires string[] Optional. Specifies what must be included (e.g., ["anonymous-client"])
target_hint string Optional. Hint as to where the transition leads: self, blank, download, etc.
referrer_policy string Optional. Policy for sending the Referer header
credentials string Optional. Indicates whether to send cookies: include, same-origin, omit

Nested field where (if source: "document"):

Key Type Description
selector_matches string CSS selector whose links are included
selector_excludes string CSS selector whose links are excluded
href_matches string URL Pattern for filtering links
href_excludes string URL Pattern for excluding links

Example for source: "document":

{
  "source": "document",
  "where": {
	"selector_matches": ".prerender-link"
  },
  "eagerness": "conservative"
}

Example for source: "list":

{
  "source": "list",
  "urls": ["/page-1", "/page-2"],
  "eagerness": "moderate"
}

Limitations

Chrome considers various factors before starting preloading:​

  • Device battery level.
  • Internet connection speed.
  • Availability of user data (e.g., cookies) for the target site.

Privacy

To protect user data during cross-domain preloads, Chrome uses:​

  • Anonymous proxy: allows hiding the user's IP address when loading resources from other domains.

  • Referrer policy: for example, strict-origin, to limit the information sent in the Referer header.​

If the user has cookies for the target site, Chrome may refuse to use preloaded content to avoid potential privacy issues.

Implementation in WordPress 6.8

In WordPress 6.8, speculative loading is enabled by default for all frontend pages, except in cases:

By default, prefetch is used with eagerness=conservative. This means that preloading is initiated at the start of a click on the link. Although the click usually occurs in a fraction of a second, this is still enough to noticeably improve performance.

Reasons why speculative loading is disabled by default for logged-in users and sites without ЧПУ ссылок:

  1. Security: Accidental loading of URLs with user parameters.
  2. Privacy: Protection of private content.
  3. Difficulty in identification: For sites without pretty permalinks, it is difficult to distinguish standard query parameters from user ones.

By default, the following types of links are excluded:

  • links to the admin panel (/wp-admin/*).
  • links to plugin and current theme files (/wp-content/themes/mytheme/*).
  • links to files from the uploads folder (/wp-content/uploads/*).
  • links to php files from the wp scope (/wp-*.php).
  • links with query parameters (/*?.+).

Why are these exceptions important?

Admin URL (/wp-admin/*) is excluded from speculative loading because prerendering dynamic and sensitive pages (login, logout, actions with nonces) can unpredictably change the state of the site or expose private data, so WordPress prefetches/prerenders only the frontend.

Example of exposing private data

  • /wp-admin/options-general.php — HTML outputs the administrator's e-mail and other sensitive fields.

  • /wp-admin/profile.php or any profile page outputs the user's name, login, e-mail, and nonce token.

The main essence of the leak here is that background loading is a different process group, which can be accessed by, for example:

  • The site's Service Worker;
  • A browser extension with webRequest permission;
  • Scripts already embedded in the admin panel (analytics, SaaS widgets) that execute during the prerender session and manage to send e-mail, login, nonce to third-party servers before the page becomes visible.

That is, another process can access the contents of a private HTML page.

Undesirable actions

For example, if preloading is triggered by hovering over a link, one can unintentionally:

  • Activate a plugin.
  • Move a post to the trash.
  • etc.

By default, WP rules look like this:

{
  "prefetch": [
	{
	  "source": "document",
	  "where": {
		"and": [
		  {
			"href_matches": "/*"
		  },
		  {
			"not": {
			  "href_matches": [
				"/wp-*.php",
				"/wp-admin/*",
				"/wp-content/uploads/*",
				"/wp-content/*",
				"/wp-content/plugins/*",
				"/wp-content/themes/mytheme/*",
				"/*\\?(.+)"
			  ]
			}
		  },
		  {
			"not": {
			  "selector_matches": "a[rel~=\"nofollow\"]"
			}
		  },
		  {
			"not": {
			  "selector_matches": ".no-prefetch, .no-prefetch a"
			}
		  }
		]
	  },
	  "eagerness": "conservative"
	}
  ]
}

Functions and Hooks in WordPress

Functions:

Hooks:

Customization in WordPress

Changing configuration

WordPress allows changing the default configuration of speculative loading — for example, you can enable prerender mode or increase eagerness (degree of "impatience" of preloading).

To do this, the filter wp_speculation_rules_configuration is used. The filter callback receives and can return:

  • null — to completely disable speculative loading.
    By default, this is provided when the user is logged in or pretty URLs (ЧПУ) are disabled.

  • An array:
    [ 'mode' => 'auto', 'eagerness' => 'auto' ]

    Allowed values:

    • mode: auto, prefetch, prerender
    • eagerness: auto, conservative, moderate, eager
      (the value immediate is not supported as it is too aggressive)

When set to auto, WordPress automatically selects the mode:

  • mode=auto - prefetch
  • eagerness=auto - conservative

Examples of changing configuration:

Example: change the eagerness mode to moderate:

add_filter( 'wp_speculation_rules_configuration', 'my_change_specrules_config' );
function my_change_specrules_config( $config ) {
	if ( is_array( $config ) ) {
		$config['eagerness'] = 'moderate';
	}

	return $config;
}

Example: enable prerender and moderate eagerness:

add_filter( 'wp_speculation_rules_configuration', 'my_change_specrules_config' );
function my_change_specrules_config( $config ) {
	if ( is_array( $config ) ) {
		$config['mode']      = 'prerender';
		$config['eagerness'] = 'moderate';
	}

	return $config;
}

Example: forcefully enable speculative loading on a site without pretty permalinks:

add_filter( 'wp_speculation_rules_configuration', 'my_change_specrules_config' );
function my_change_specrules_config( $config ) {
	if ( ! $config && ! get_option( 'permalink_structure' ) ) {
		$config = [
			'mode'      => 'auto',
			'eagerness' => 'auto',
		];
	}

	return $config;
}

Example: forcefully enable for logged-in users:

add_filter( 'wp_speculation_rules_configuration', 'my_change_specrules_config' );
function my_change_specrules_config( $config ) {
	if ( ! $config && is_user_logged_in() ) {
		$config = [
			'mode'      => 'auto',
			'eagerness' => 'auto',
		];
	}

	return $config;
}

Excluding URLs

Some plugins use "action URLs" — clicking on a link changes the state of the site — for example, adding a product to the cart. Such URLs are important to exclude from speculative loading (prefetch/prerender), especially with aggressive eagerness, when the user hovers over the link and the action will be performed.

Custom paths can be excluded using the filter wp_speculation_rules_href_exclude_paths.

For example, exclude all URLs that start with /cart/:

add_filter( 'wp_speculation_rules_href_exclude_paths', 'my_specrules_exclude' );
function my_specrules_exclude( $exclude ) {
	$exclude[] = '/cart/*';
	return $exclude;
}

You can also check the current loading type and, for example, add rules only for prerender type:

add_filter( 'wp_speculation_rules_href_exclude_paths', 'my_specrules_exclude', 10, 2 );
function my_specrules_exclude( $exclude, $mode ) {
	if ( 'prerender' === $mode ) {
		$exclude[] = '/personalized-area/*';
	}
	return $exclude;
}

All paths must conform to the URL Pattern specification and are considered relative to the root. WordPress will automatically add the prefix if the site is installed in a subdirectory.

Adding additional rules

By default, one main rule is used. But you can add additional custom rules through the hook wp_load_speculation_rules.

The hook passes the WP_Speculation_Rules object to the callback function, which can be worked with — for example, to add new rules with the desired configuration.

Example: enable prerender with eagerness = moderate for specific URLs, without changing the overall behavior of the site:

add_action( 'wp_load_speculation_rules', 'my_additional_specrules' );
function my_additional_specrules( WP_Speculation_Rules $specrules ): void {
	$specrules->add_rule(
		'prerender',
		'my-moderate-prerender-url-rule',
		[
			'source'    => 'list',
			'urls'      => [
				'/some-url/',
				'/another-url/',
				'/yet-another-url/',
			],
			'eagerness' => 'moderate',
		]
	);
}

If the list of URLs changes frequently, you can instead use a document-level rule — it will apply to all links with a specific CSS class:

add_action( 'wp_load_speculation_rules', 'my_additional_specrules' );
function my_additional_specrules( WP_Speculation_Rules $specrules ): void {
	$specrules->add_rule(
		'prerender',
		'my-moderate-prerender-optin-rule',
		[
			'source' => 'document',
			'where'  => [
				'selector_matches' => '.moderate-prerender, .moderate-prerender a',
			],
			'eagerness' => 'moderate',
		]
	);
}

Now you can add the class .moderate-prerender to a block or link — the rule will apply: prerender with eagerness = moderate.

More details about the format of rules can be found in the Speculation Rules API specification.

Customization through UI

WordPress supports two special CSS classes for managing preloading:

  • no-prefetch — disables prefetch and prerender;
  • no-prerender — disables only prerender.

Many blocks in the editor have a "Additional CSS classes" field in the "Additional" meta box.

Add these classes to the block to disable speculative loading for all links within it.

This method allows flexible management of speculative loading directly through the post editor UI.

no-prefetch disables all speculative loading, as prerender includes prefetch.

Definition in JS

Checking Support for Speculation API

You can check if the browser supports the Speculation Rules API like this:

if (
  HTMLScriptElement.supports &&
  HTMLScriptElement.supports("speculationrules")
) {
  console.log( "Your browser supports the Speculation Rules API." );
}

Detecting prefetch mode in JS

When the page is loaded with prefetch, the deliveryType property of the PerformanceResourceTiming object will return the value "navigational-prefetch". You can use such code to execute a function if the page was prefetched:

if (
  performance.getEntriesByType("navigation")[0].deliveryType ===
  "navigational-prefetch"
) {
	respondToPrefetch();
}

This method is useful for delaying actions that may cause issues during prefetching (see Unsafe prefetching).

Detecting prerender mode in JS

To perform an action during prerender, you can check the document.prerendering property. For example, to send analytics data:

if( document.prerendering ){
  analytics.sendInfo( 'Page in prerender mode' );
}

Check whether the page is in prerender mode or was a prerender page:

function pagePrerendered() {
  return (
	document.prerendering ||
	performance.getEntriesByType("navigation")[0]?.activationStart > 0
  );
}

When the prerender document is activated, PerformanceNavigationTiming.activationStart gets a value of type DOMHighResTimeStamp, representing the time between the start of prerender and the actual activation of the document.

Running JS in normal mode (when prerender)

If you need to run JS code only when the user actually navigates to the page, and not in prerender mode, you can use:

For example, let’s run the analytics script only on a real visit to the page:

if( document.prerendering ){
	document.addEventListener( "prerenderingchange", initAnalytics, { once: true } );
} else {
	initAnalytics();
}

Determining the frequency of prerender activation

To measure how often prerender activation occurs, you can combine all three APIs:

  • document.prerendering — to determine whether the page is currently in prerender mode.
  • prerenderingchange — to track activations in this case.
  • activationStart — to check whether the page was prerendered in the past.
if (document.prerendering) {
	document.addEventListener(
		'prerenderingchange',
		() => console.log('Page activated from prerender mode'),
		{ once: true }
	);
}
else if (performance.getEntriesByType('navigation')[0]?.activationStart > 0) {
	console.log('The page was prerendered and already active when this script executed');
}
else {
	console.log('Page loaded normally (no prerender)');
}

Debugging

Debug in JS

The prerenderingchange event is a great way to run code upon page activation if you don't want that code to run in the background before the actual transition. This can be useful, for example, for analytics and advertising.

//
document.addEventListener( 'prerenderingchange', loadWhenPageIsActivated );
function loadWhenPageIsActivated(){
	console.log( `Page activated!` );

	const activationStart = Math.round( performance.getEntriesByType('navigation')[0].activationStart );
	console.log( `Activation time: ${activationStart} ms` );
}

Now if you navigate to a prerender page, for example, click the mouse button on a link, wait 3 seconds, and release, we will be redirected to the page that was prerendered, and we will see in the DevTools console:

Page activated!
Activation time: 3500 ms (activated 3500 ms after the start of loading)

The order of logs shows that the activation event fires first, before LCP appears. Although the page is already fully loaded by that time, LCP requires "painting" — this is reflected in the name! That is, the page loads in the background during prerender, but the actual rendering only begins after activation. Therefore, there will be a slight delay between the activation moment and the LCP time (for example, 100 ms). Which, in essence, is instantaneous for the user!

Debugging with DevTools

Logging to the console (console.log) is everything! But you must know that Chrome DevTools has support for speculative loading.

On the Application tab in DevTools, there is a section Speculative loads:

Browser Support

The Speculation Rules API is supported in the following browsers:

  • Chrome 121+
  • Edge 121+
  • Opera (based on Chromium)

In other browsers (e.g., Firefox, Safari), the API is ignored without negative consequences.

Conclusion

Speculative loading is a powerful optimization tool that can significantly speed up page loading (of the site) through the preloading of content. Using the Speculation Rules API (speculative loading rules) in conjunction with prefetch and prerender mechanisms enhances site performance, improves LCP metrics, and overall interface responsiveness. The implementation of this technology is particularly important for SEO, Core Web Vitals, and improving user experience. With flexible customization through WordPress hooks, the ability to exclude sensitive URLs, and customize rules through filters and classes, speculative loading becomes a reliable solution for modern WordPress sites. If you want to increase site speed, improve PageSpeed metrics, and ensure smooth navigation — be sure to activate and configure speculative loading today.