Speculatr for Craft CMS

Usage

Most sites never write any Twig for Speculatr — the settings cover it. Everything on this page is for the cases where they do not.

Opting one link out

Give it the opt-out class:

<a href="/raffle/enter" class="no-speculation">Enter the raffle</a>

rel="nofollow", download and target="_blank" links are already excluded by default, as are links to files rather than pages. If you need a different rule, excludeSelectors takes any CSS selector and matches it against the link itself:

'excludeSelectors' => ['[data-modal]', 'a[href$="/apply"]'],

Guarding work that should only happen for real visitors

This is the important one. A prerender runs your page — templates, JavaScript, analytics — in a hidden tab, before anybody has clicked anything. Anything that should happen when a person arrives needs guarding:

{% if not craft.speculatr.isPrerender %}
    {% do entry.recordView() %}
{% endif %}
craft.speculatr.isSpeculativeeither kind — the browser is guessing
craft.speculatr.isPrefetchthe HTML is being downloaded; nothing on the page is running
craft.speculatr.isPrerenderthe whole page is being loaded and run in a hidden tab

These read the Sec-Purpose request header, which is prefetch for a prefetch and prefetch;prerender for a prerender. Note the prerender value contains the prefetch token — so isPrefetch is deliberately false during a prerender, which is what you want when you are asking "is this a cheap body fetch or a full page run".

Browsers already defer a good deal on your behalf: a prerendered page is hidden, so anything gated on the Page Visibility API holds off until activation, and Google Analytics 4 handles this correctly without help. It is your own counters, one-shot side effects and third-party scripts that need the guard.

Speculating specific URLs

A page often knows something the settings cannot — that this listing's first result is where nearly everybody goes next, for instance.

{% do craft.speculatr.prefetch(entries|first) %}
{% do craft.speculatr.prefetch(['/contact', '/pricing']) %}
{% do craft.speculatr.prerender(entry, 'moderate') %}

Both take a URL, an element, or an array of either. prefetch() defaults to immediate eagerness — naming a URL explicitly is already the decision the eagerness levels exist to defer. prerender() defaults to moderate, and should be used sparingly: each one is a whole page load spent before anybody has clicked, and Chrome will only hold a couple at a time.

A URL named in more than one place is emitted once.

Excluding something for one page only

{% do craft.speculatr.exclude('search/*') %}
{% do craft.speculatr.exclude('cart/*', 'This listing links straight into the cart') %}

For the exclusion that is true of the page you are on rather than of the whole site — a listing whose links carry a filter parameter, say. The second argument is the reason, and it shows up in the control panel.

Turning it off for one page

{% do craft.speculatr.disable() %}

For a page whose links are all one-shot. excludePages in the settings does the same thing by URI pattern if it is not a per-template decision.

Placing the script yourself

The rules are injected automatically just before </body>. If you want them somewhere specific:

{{ craft.speculatr.tag() }}

And to look at what is being sent:

<pre>{{ craft.speculatr.json() }}</pre>
{% set document = craft.speculatr.document() %}

Asking about a URL

{% set verdict = craft.speculatr.explain('/checkout') %}

{% if verdict.speculated %}
    {{ verdict.summary() }}          {# "prerender (moderate)" #}
{% else %}
    {{ verdict.reason }}             {# "Configured in Speculatr's settings" #}
{% endif %}

verdict.blockedBy is the exclusion that stopped it, when one did. verdict.caveats lists what a URL on its own cannot settle — chiefly that a selector exclusion may still apply to the actual link.

The control panel

Speculatr → Rules answers the only question anybody really asks about this plugin, which is not "what are my rules" but is it going to prerender the thing that signs people out.

  • the exact JSON being sent, for signed-out visitors or for you
  • every exclusion, with its source — Craft's own configuration, Speculatr's defaults, your settings — and the reason it exists
  • a box you can paste a URL into, which tells you what would happen and names the exclusion that stopped it

The console

php craft speculatr/rules/show              # the rules document
php craft speculatr/rules/exclusions        # everything excluded, and why
php craft speculatr/rules/check /checkout   # what happens to one URL

All three take --logged-in to answer as a signed-in user rather than a guest, which is the quick way to confirm your downgrade is doing what you think.

$ php craft speculatr/rules/check /logout

  /logout
  ───────
  never speculated
  The logout URL (`logoutPath`)