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:
- The user is logged in.
- Pretty permalinks are disabled ЧПУ ссылки.
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 ЧПУ ссылок:
- Security: Accidental loading of URLs with user parameters.
- Privacy: Protection of private content.
- 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:
- wp_get_speculation_rules_configuration() — Returns the speculation rules configuration.
- wp_get_speculation_rules() — Returns the full speculation rules data based on the configuration.
- wp_print_speculation_rules() — Prints the speculation rules.
- WP_Speculation_Rules() — Class representing a set of speculation rules.
Hooks:
- wp_speculation_rules_configuration — Filters the way that speculation rules are configured.
- wp_speculation_rules_href_exclude_paths — Filters the paths for which speculative loading should be disabled.
- wp_load_speculation_rules — Fires when speculation rules data is loaded, allowing to amend the rules.
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:
- the document.prerendering property to check that the page is not in
prerender
- and the prerenderingchange event, which fires when the user navigates to a prerender page.
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
Current data: https://caniuse.com/?search=speculation
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
Related materials:
MDN: https://developer.mozilla.org/en-US/docs/Web/API/Speculation_Rules_API
MDN (HTTP Header): https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Speculation-Rules
Google: https://codelabs.developers.google.com/speculation-rules
Google: https://developer.chrome.com/docs/web-platform/prerender-pages
WPORG: https://make.wordpress.org/core/2025/03/06/speculative-loading-in-6-8/
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.