Troubleshooting
No rules appear in the page at all
Work down this list; it is roughly in order of likelihood.
- You are signed in. Signed-in visitors get nothing by default. Open the page in a private window, or turn on Admins in the settings to try it on yourself.
- The page is not HTML. Speculatr only touches responses whose
Content-Typeistext/html, so afeed.rss.twigor amanifest.json.twigis correctly left alone. - The response is not a 200. A 404 or a redirect carries no rules.
- The page has no
</body>. A fragment — an htmx swap, an Element API payload rendered as HTML — is left alone rather than having a script element stapled to the end of it. - The URL carries a preview or share token. A page you are looking at through Live Preview or a share link never carries rules; speculating from it would spend the token on links nobody clicked.
- The page matches
excludePages. - A template called
craft.speculatr.disable(). - You are looking at a prefetched copy. If you fetched the page with a
Sec-Purposeheader, you correctly got no rules — seeskipOnSpeculative. deliveryis set toheader. The rules are in aSpeculation-Rulesresponse header pointing at/speculatr/rules.json, not in the page. Check the headers, not the source.
php craft speculatr/rules/show prints what a guest would get, which separates "the rules are wrong"
from "the rules never reached this page".
The rules are there but nothing ever speculates
- Check the browser. This only works in Chromium — Chrome, Edge, Opera, Android — from version
- Safari and Firefox ignore it entirely and always will until they ship it.
- Check
chrome://predictorsand the Application → Speculative loads panel in Chrome DevTools. It lists every rule, every URL it matched, and — most usefully — the reason any speculation was rejected or discarded. - Chrome opts out on its own under Data Saver, on a low battery, when memory is short, or when "Preload pages" is off in the reader's settings. Nothing is wrong; the browser has decided not to.
- Only two at a time. At any eagerness other than
immediate, Chrome holds two speculations and discards the oldest. Hovering across a navigation bar will start and throw away a lot of them, and that is the intended behaviour. - Your Content-Security-Policy is blocking it. See below.
A Content-Security-Policy is blocking the inline rules
Inline speculation rules need an explicit allowance. Either add it:
Content-Security-Policy: script-src 'self' 'inline-speculation-rules'
…or set Delivery to Speculation-Rules header, which puts the rules in a JSON document at
/speculatr/rules.json and needs no inline allowance at all.
The browser gives no visible error for this beyond a CSP violation report, so it is worth checking the console before concluding the rules are wrong.
Something is being speculated that should not be
Paste the URL into Speculatr → Rules, or run
php craft speculatr/rules/check <url>. If it comes back speculated and you disagree, add it — as a
path, a query parameter, or a CSS selector on the link.
Remember that a selector exclusion cannot be evaluated from a URL alone. The checker says so rather
than pretending; a link wearing .no-speculation is excluded even though its URL is not.
Craft Commerce deserves a specific mention: its action requests are already excluded, but a cart
or checkout page with a URI of its own is not, because Speculatr has no way to know what you called
it. Add it to excludePaths.
Visitors get no speculation behind a CDN, intermittently
This is the one that is genuinely hard to diagnose from the symptom, so it is worth understanding before it happens.
When skipOnSpeculative is on, a page fetched by a prefetch is served without rules. That means the
response varies on the Sec-Purpose request header. A shared cache that has not been told will store
the rules-free copy a prefetch received and hand it to the next real visitor, who then gets no
speculation at all — and it will look random, because it depends which request populated the cache.
Speculatr sends Vary: Sec-Purpose to prevent exactly this. If you have stripped or overridden Vary
at the edge, put it back. If your CDN cannot vary on that header, turn skipOnSpeculative off instead
— serving rules to a speculated page is a much smaller problem than serving a stale rules-free page to
everybody.
Prefetches are not being used when readers arrive from a campaign
A prefetch of /pricing only satisfies a navigation to /pricing?utm_source=newsletter if the
response says the parameter does not matter. That is what ignoreTrackingParams does, and it needs
both halves to work: the No-Vary-Search response header and the matching
expects_no_vary_search in the rules.
Check that the header is actually reaching the browser. A CDN or a reverse proxy that strips unknown response headers will remove it, and the symptom is simply that the prefetch is never a hit.
Analytics are counting views nobody made
A prerender runs your page. Google Analytics 4 handles this correctly on its own — it waits for activation via the Page Visibility API — but your own counters and some third-party scripts will not.
{% if not craft.speculatr.isPrerender %}
{% do entry.recordView() %}
{% endif %}
If the numbers went up rather than down, that is the direction to look.
The page is truncated, or ends mid-tag
Speculatr restamps Content-Length after adding the script element, so this should not happen. If it
does, something else is also modifying the response after Response::EVENT_AFTER_PREPARE and is not
restamping. Turn Speculatr off to confirm, then look at the other plugin.
Nothing at all works after an upgrade
php craft clear-caches/compiled-templates
php craft speculatr/rules/show
If show prints a sane document, the plugin is fine and the problem is in delivery — full-page
caching, a CDN, or a template that stopped rendering </body>.