How to Use the Speculation Rules API on Shopify

Last updated
Expert reviewed
5 min read
Jacques Blom
Jacques Blom
CTO at Fudge.

Key takeaways

  • Shopify already ships speculation rules on every storefront. It serves a Speculation-Rules response header pointing at a JSON file that prefetches product, collection, page, search, blog and policy URLs at moderate eagerness.
  • Shopify only prefetches. It never prerenders. Adding your own prerender rule in theme.liquid is the real opportunity, and Shopify’s own theme docs say themes may add extra rules.
  • Prefetch downloads the HTML. Prerender renders the whole page in a hidden tab, subresources and JavaScript included. Prerender is far faster and far more expensive, so scope it tightly.
  • eagerness controls the trigger: immediate, eager, moderate (200ms hover on desktop) and conservative (pointer down). Document rules default to conservative, list rules to immediate.
  • The real risks on Shopify are cart and discount side effects, double-counted analytics, and third-party scripts firing before the shopper arrives. Exclude /cart, /account, /checkouts and /discount paths, and gate analytics on document.prerendering.

The Speculation Rules API lets a page tell the browser which URLs the visitor is likely to open next, so the browser can fetch or fully render them in advance. On Shopify it is one of the few remaining ways to make a navigation feel instant rather than merely fast.

Most guides to speculation rules assume you are starting from zero. On Shopify you are not. The platform already injects a ruleset on your behalf, and the useful work is knowing what it covers, what it deliberately leaves out, and what you can safely add in theme.liquid on top.

Why you can trust us

We have been in the Shopify space for over four years and have worked with hundreds of Shopify brands on their storefronts. Jacques has over 15 years of development experience. We build Fudge, an AI storefront editor with a 5.0 rating on the Shopify App Store and Built for Shopify status, so we work in the theme layer where these rules live.


What is the Speculation Rules API?

It is a JSON block inside a <script type="speculationrules"> tag. The JSON names URLs, or a pattern that matches links in the document, and tells the browser to prefetch or prerender them.

<script type="speculationrules">
{
  "prerender": [
    { "where": { "href_matches": "/products/*" }, "eagerness": "moderate" }
  ]
}
</script>

It replaces the old resource hints. <link rel="prefetch"> only warmed the HTTP cache and gave you no control over timing. <link rel="prerender"> was never implemented consistently and is deprecated. Speculation rules give you pattern matching, exclusions, per-rule triggers, and a defined set of restrictions on what a speculated page is allowed to do.

The rules are a hint, not a command. A browser that does not understand the script tag ignores it. Chrome itself declines to speculate when Save-Data is on, when the device is low on memory, when Energy Saver is active on a low battery, or when the user has turned page preloading off.1 Nothing breaks when speculation does not happen. The visitor just gets a normal navigation.

Prefetch versus prerender

PrefetchPrerender
What is fetchedThe HTML document onlyDocument, subresources, and JavaScript
What runsNothingThe full page, in a hidden tab
CostOne extra GETRoughly an extra tab
Typical gainRemoves server and network timeCan approach a zero-millisecond LCP
Side-effect riskLowHigh
Use itBroadlyNarrowly, on high-confidence links

Prefetch is the safe default. Prerender is where the dramatic numbers come from, and also where every risk in this guide lives.


What speculation rules does Shopify already run?

Shopify rolled speculation rules out platform-wide in late June 2025 and reported an average improvement of 130ms on desktop and 180ms on mobile across all percentiles and all loading metrics, meaning TTFB, FCP and LCP together.2

The rules are not in your theme. Shopify sends a Speculation-Rules response header pointing at a JSON file on its CDN, served as application/speculationrules+json. Fetch that file on any live storefront and you get the current platform ruleset:

{
  "tag": "shopify_storefront_moderate",
  "prefetch": [
    {
      "where": {
        "or": [
          { "href_matches": "/(products|collections|pages|search|shop|blogs|policies){/*}?" },
          { "href_matches": "/([a-z]{2,3}|zh-hans|zh-hant)(-[a-z]{2,3})?/(products|collections|pages|search|shop|blogs|policies){/*}?" },
          { "href_matches": "/" },
          { "href_matches": "/([a-z]{2,3}|zh-hans|zh-hant)(-[a-z]{2,3})?{/}?" }
        ]
      },
      "eagerness": "moderate"
    }
  ]
}

Three things are worth reading out of that block.

It is prefetch only. There is no prerender key anywhere in it. Shopify is buying the document early and nothing more.

It is an allowlist, not a blocklist. Only products, collections, pages, search, shop, blogs, policies and the homepage match, each in both root and market-prefixed form. Cart, account and checkout URLs never match, so they are never speculated.

The eagerness is moderate. Shopify started at conservative and later moved the default up, reporting median desktop gains of 285ms TTFB, 224ms FCP and 228ms LCP, with roughly 10% of speculated navigations arriving at a 0ms TTFB. Mobile gains were much smaller at around 25ms, 20ms and 24ms. The cost was a 14% increase in total HTML requests from supporting browsers.3

Shopify’s theme performance docs confirm themes are allowed to add rules of their own on top of this. So your job is not to reimplement prefetching. It is to decide where prerendering earns its keep.


Eagerness levels, and which one to pick

eagerness is the trigger, not the priority. It answers “when does the browser act on this rule.”

ValueDesktop triggerChrome limit per page
immediateAs soon as the rule is parsed50 prefetch, 10 prerender
eager10ms of pointer hover2, first in first out
moderate200ms of pointer hover, or pointer down2, first in first out
conservativePointer or touch down only2, first in first out

Mobile has no hover, so Chromium falls back to viewport heuristics. eager on mobile fires shortly after an anchor enters the viewport, behaviour that changed in Chrome 143; before that, eager behaved like immediate. moderate on mobile waits until scrolling settles.

Defaults differ by rule type. A list rule with urls defaults to immediate. A document rule with where defaults to conservative. If you write a document rule and forget eagerness, you get the most cautious behaviour, which is usually not what you wanted.

For a Shopify storefront, moderate is the sensible starting point for prerender. immediate prerender on a collection page would try to render up to ten product pages that the shopper may never open, on a device you do not control.


Document rules versus list rules

List rules name URLs directly. They suit a known, fixed next step.

{ "prefetch": [ { "urls": ["/collections/all", "/pages/size-guide"] } ] }

Document rules match links already in the page using where. They suit a storefront, where the interesting URLs are generated by Liquid and change per page.

href_matches takes URL Pattern syntax, so * is a wildcard and {...}? marks an optional group. selector_matches takes a CSS selector, which is how you opt individual links out with a class. Both accept arrays, and and, or and not compose them.

That combination is what makes exclusions practical. You can say “every link on the page except the ones that change server state.”


How to add speculation rules to a Shopify theme

Rules go in the layout so they exist on every page that uses it. Open your theme code editor, edit layout/theme.liquid, and place the script just before the closing </head> tag. If you have not edited theme files before, start with our guide on how to edit a Shopify theme, and duplicate the theme before you touch it.

The narrow version: prerender products from browse pages

This is the version to start with. It prerenders product pages, and only from the templates where a shopper is genuinely picking a product.

{%- if request.page_type == 'collection'
   or request.page_type == 'index'
   or request.page_type == 'search' -%}
  <script type="speculationrules">
  {
    "tag": "theme-product-prerender",
    "prerender": [
      {
        "where": {
          "and": [
            { "href_matches": [
                "/products/*",
                "/([a-z]{2,3}|zh-hans|zh-hant)(-[a-z]{2,3})?/products/*"
              ]
            },
            { "not": { "selector_matches": ".no-prerender" } },
            { "not": { "selector_matches": "[rel~=nofollow]" } }
          ]
        },
        "eagerness": "moderate"
      }
    ]
  }
  </script>
{%- endif -%}

The Liquid if uses request.page_type, which Shopify sets to values like collection, index, search, product and cart. Gating on it keeps the rule off templates where prerendering a product page is pointless.

The second href_matches entry handles markets. Shopify prefixes localised URLs with a locale segment, and the group here is copied from Shopify’s own rules file so the two behave the same way.

The .no-prerender escape hatch matters. Add that class to any link a merchandiser later decides should not be speculated, and no code change is needed.

The broad version: everything except the dangerous paths

If you want wider coverage, invert the logic. Match all links, then subtract the routes that mutate state.

<script type="speculationrules">
{
  "tag": "theme-broad-prerender",
  "prerender": [
    {
      "where": {
        "and": [
          { "href_matches": "/*" },
          { "not": { "href_matches": [
              "/cart{/*}?",
              "/checkouts/*",
              "/account{/*}?",
              "/discount/*",
              "/apps/*",
              "/([a-z]{2,3}|zh-hans|zh-hant)(-[a-z]{2,3})?/cart{/*}?",
              "/([a-z]{2,3}|zh-hans|zh-hant)(-[a-z]{2,3})?/account{/*}?",
              "/([a-z]{2,3}|zh-hans|zh-hant)(-[a-z]{2,3})?/discount/*"
            ]
          }},
          { "not": { "href_matches": "/*\\?*(^|&)logout=*" } },
          { "not": { "selector_matches": "[rel~=nofollow]" } },
          { "not": { "selector_matches": ".no-prerender" } }
        ]
      },
      "eagerness": "moderate"
    }
  ]
}
</script>

/discount/* deserves its own line. Shopify discount links apply a code and set a cookie on a plain GET, so prerendering one applies the discount before the shopper has clicked anything.

Test both versions on an unpublished duplicate theme first. A speculation rule is easy to write and easy to get subtly wrong.

Want theme code you can actually read and keep? Describe the change to Fudge.
Try Fudge for Free

Browser support in 2026

Speculation rules are a Chromium feature. Prerendering via the API shipped in Chrome 109, and Edge, Opera and Samsung Internet followed on the same engine.

BrowserStatus
ChromeSupported from 109
EdgeSupported from 109
OperaSupported from 95
Samsung InternetSupported from 21
FirefoxNot supported. Mozilla’s standards position on speculation rules is neutral, citing complexity
SafariImplemented but disabled by default as of 26.2, with active WebKit work through 2026

Global support sits around 75% of tracked traffic.4 For a Shopify store that skews to iOS Safari, the reachable share is materially lower, which is a reason to keep the cost of your rules low rather than a reason to skip them.

Non-supporting browsers ignore the script tag entirely. There is no polyfill, no fallback needed, and no penalty beyond a few hundred bytes of markup.


The risks that actually bite on Shopify

Wasted bandwidth and extra origin load

Every speculation that is not followed by a click is a request nobody used. Shopify measured a 14% rise in HTML requests just from moving prefetch eagerness up one notch. Prerender is heavier again because it pulls subresources and runs scripts.

Shopify absorbs the server side for you, but your apps and third-party endpoints do not. If a product page calls a reviews API or a personalisation service on load, prerendering that page calls them too.

Analytics double counting

A prerendered page runs its JavaScript before the shopper has seen it. Left alone, that inflates pageviews and distorts every downstream rate.

The fix is to defer anything that records or fires until activation:

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

Google Analytics handles prerender natively. Custom tracking, chat widgets, popup timers and A/B test bucketing generally do not. Audit the scripts your theme loads before you turn prerendering on, and if that inventory looks unfamiliar, our guide to render-blocking scripts in Shopify is a good way to enumerate them.

Cart and session side effects

This is the Shopify-specific hazard. Any URL that changes state on a GET request is unsafe to speculate: discount links, ?logout= links, cart permalinks, add-to-cart query parameters, referral and affiliate landing URLs. Shopify’s own ruleset sidesteps the whole problem by allowlisting read-only routes. Copy that instinct.

You cannot filter server-side in Liquid

Browsers send Sec-Purpose: prefetch or Sec-Purpose: prefetch;prerender on speculative requests, and a normal backend can branch on that header. Liquid cannot. The request object exposes only host, origin, path, page_type, locale, design_mode and visual_preview_mode, with no access to request headers. On a Shopify theme, every guard has to live in the rule itself or in client-side JavaScript.

Overlap with existing theme prefetch scripts

Plenty of paid themes still ship a hover-preload script, either instant.page or a homegrown copy of it. instant.page preloads after 65ms of hover using its own fetch, entirely separate from the browser’s speculation machinery. Running it alongside Shopify’s moderate prefetch means two systems requesting the same document on roughly the same trigger.

Search your theme assets for instant.page and for rel="prefetch" before adding rules. If a script is duplicating what the platform already does, remove it. See how to add custom JavaScript in Shopify for where these snippets usually hide.


How to measure whether it worked

Chrome DevTools. Open the Application panel and find Speculative loads under Background services. It has three tabs: the status of the current page, every ruleset found on the page, and every URL speculated from those rulesets with its outcome. Failures come with reasons, such as a non-2xx response or a limit already reached. Reload the page after opening the panel or it stays empty.

Field data. CrUX has broken metrics out by navigation type since March 2024, and prerender is one of the types alongside navigate, back_forward_cache and restore. That is the honest way to see how much of your real traffic is landing on a prerendered page and what its LCP looks like compared with a cold navigation.

What to expect. Prerender mainly moves LCP, because the page is already painted when the click lands. INP can improve as a knock-on effect, since scripts have already parsed and executed. Neither metric moves for the share of visitors on Safari or Firefox, so a site-wide average will always understate the effect for Chrome users.

Speculation rules are a navigation optimisation, not a page-weight fix. A slow product page is still slow the first time someone sees it. Pair this with the fundamentals in how to speed up a Shopify theme, and see the state of Shopify performance in 2026 for where typical stores currently sit.


Where Fudge fits

Speculation rules are a small block of JSON, but the work around them is theme work: finding the right place in theme.liquid, gating on request.page_type, auditing which scripts fire on load, and stripping out a legacy preload script that now duplicates the platform.

Fudge is an AI storefront editor that writes native Liquid, CSS and JavaScript straight into your theme. You can ask it to add a prerender ruleset scoped to product links on collection pages, or to find and remove the hover-preload script your theme shipped with, and read the diff before you publish.

Because the output is theme code rather than an app-rendered layer, there is no extra script tag from a vendor and nothing disappears if you uninstall. The same applies to everything else you build with the Shopify store editor.


FAQ

Does Shopify already use the Speculation Rules API?

Yes. Shopify rolled speculation rules out platform-wide in late June 2025 and serves them via a Speculation-Rules response header pointing at a JSON file on its CDN. The platform ruleset prefetches product, collection, page, search, shop, blog, policy and homepage URLs at moderate eagerness, in both root and market-prefixed form. It does not prerender anything.

Should I use prerender or prefetch on a Shopify store?

Shopify already prefetches the safe read-only routes for you, so adding more prefetch rules mostly duplicates work. Prerender is the addition worth making, because it renders the page fully in a hidden tab and can bring LCP close to zero. Scope it narrowly, such as product links on collection and search pages, and use moderate eagerness.

Where do I put speculation rules in a Shopify theme?

In layout/theme.liquid, just before the closing head tag, so the rules exist on every page that uses the layout. Wrap the script in a Liquid condition on request.page_type if you only want it on certain templates. Duplicate your theme and test on the unpublished copy before publishing.

Will speculation rules break my analytics?

Prerendering can inflate pageviews because the page runs its JavaScript before the shopper sees it. Google Analytics handles prerender natively, but custom tracking, chat widgets and A/B test scripts usually do not. Gate them behind a document.prerendering check and a prerenderingchange listener so they only fire on activation.

Does the Speculation Rules API work in Safari and Firefox?

Not yet. The API works in Chrome and Edge from version 109, Opera from 95 and Samsung Internet from 21, covering roughly 75% of tracked traffic. Safari has an implementation that is disabled by default as of 26.2, and Firefox has not shipped it, with Mozilla's standards position recorded as neutral. Browsers without support ignore the script tag, so nothing breaks.

Can prerendering add items to the cart or log a customer out?

It can if you speculate the wrong URLs. Any route that changes state on a GET request is unsafe, including Shopify discount links that set a cookie, logout query parameters, cart permalinks and add-to-cart parameters. Exclude /cart, /account, /checkouts and /discount paths in your rule, in both root and locale-prefixed form.

How do I check whether speculation rules are working?

Open Chrome DevTools, go to the Application panel and find Speculative loads under Background services. It lists every ruleset on the page, every URL speculated from them, and the outcome with a failure reason where relevant. Reload the page after opening the panel. For field data, CrUX reports metrics split by navigation type, including prerender.

Jacques's signature
Ship theme changes without the guesswork.

Footnotes

  1. Chrome for Developers, “Prerender pages in Chrome for instant page navigations” - conditions under which Chrome declines to prerender, plus eagerness triggers and per-page speculation limits. https://developer.chrome.com/docs/web-platform/prerender-pages

  2. Performance @ Shopify, “Speculation Rules at Shopify” - platform-wide rollout in late June 2025, reporting an average 130ms improvement on desktop and 180ms on mobile across all percentiles and all loading metrics. https://performance.shopify.com/blogs/blog/speculation-rules-at-shopify

  3. Performance @ Shopify, “Faster storefront navigations with moderate speculation rules” - median desktop gains of 285ms TTFB, 224ms FCP and 228ms LCP after moving from conservative to moderate eagerness, against a 14% rise in total HTML requests from supporting browsers. https://performance.shopify.com/blogs/blog/faster-storefront-navigations-with-moderate-speculation-rules

  4. Can I Use, “Speculation Rules API” - approximately 75% global support, with Chrome and Edge from 109, Opera from 95 and Samsung Internet from 21. https://caniuse.com/mdn-html_elements_script_type_speculationrules

You might also be interested in

Building a Multi-Currency Shopify Storefront Without Sacrificing Speed
Multi currency Shopify speed depends on native Markets vs a converter app. How presentment currencies, rounding, and CLS decide your Core Web Vitals.
Sticky Add to Cart on Shopify PDPs (2026)
Sticky add to cart on Shopify keeps price, variant, and buy button visible as shoppers scroll. How to build it natively with no app.
How to Build a Shopify Quiz Landing Page (Step-by-Step)
A Shopify quiz landing page hosts the quiz and nothing else. Build the page and template, strip the header, speed it up, and track the right events.