Ga naar de inhoud
Snelheid

Prefetchen bij hover: de volgende pagina staat klaar voordat je klikt

Luremo

Craft CMS studio

Twee tijdlijnen. Zonder prefetch begint het wachten op de server pas na de klik; met prefetch haalt de browser de pagina al op tijdens de hover, zodat ze na de klik direct in beeld staat.
Zonder prefetch begint het wachten bij de klik. Met prefetch is het dan al voorbij.

Met Speculation Rules haalt de browser een pagina al op zodra je muis boven een link hangt. Hoe dat werkt, welke browsers meedoen en hoe we het op deze site hebben ingebouwd, met een speeltuin om het zelf te proberen.

Waarom een klik altijd even duurt

Klik je op een link, dan begint je browser pas op dat moment aan de volgende pagina. Hij vraagt de HTML op bij de server en wacht tot de eerste bytes binnenkomen. Pas daarna kan hij stylesheets, lettertypen en afbeeldingen ophalen. Dat eerste stuk, van klik tot eerste byte, heet de time to first byte. Op een snelle server is dat een fractie van een seconde, maar over een mobiele verbinding of bij een drukke server loopt het snel op.

Tussen het aanwijzen van een link en de klik zit meestal een paar honderd milliseconden. Dat is precies genoeg om dat eerste stuk alvast te doen. Dat is het idee achter prefetchen: de browser haalt de pagina op terwijl de bezoeker nog beslist, zodat de klik alleen nog hoeft te tonen wat er al ligt.

Speculation Rules: de browser een hint geven

De Speculation Rules API is een set regels in JSON waarmee je de browser vertelt welke links hij vooruit mag laden. Je kunt losse URL's opgeven, maar handiger zijn document rules. Die beschrijven met URL-patronen en CSS-selectors welke links op de pagina in aanmerking komen. Wanneer de browser een regel uitvoert, hangt af van de eagerness:

  • immediate: meteen, zodra de regels binnen zijn.
  • eager: bij het kleinste signaal. Op desktop na 10 ms hover; op een telefoon, waar hover niet bestaat, zodra een link 50 ms in beeld staat.
  • moderate: op desktop na 200 ms hover, of eerder als je de muisknop al indrukt. Op een telefoon kijkt Chrome welke links nog in beeld staan als je een halve seconde gestopt bent met scrollen.
  • conservative: pas als je de muisknop indrukt of het scherm aanraakt.

Er zijn twee soorten speculatie. Prefetch haalt alleen de HTML van de pagina op en voert niets uit. Prerender gaat verder: de browser bouwt de hele pagina onzichtbaar op, inclusief JavaScript, zodat ze bij de klik meteen verschijnt. Dat is sneller, maar ook ingrijpender, want scripts draaien dan voor een pagina die de bezoeker misschien nooit ziet.

Wij gebruiken eager. De prefetch begint dan zodra de muis een link raakt, en in de tijd tot de klik is de pagina bijna altijd al binnen. Dat heeft een prijs: elke link waar de muis overheen gaat, wordt één keer opgehaald, ook als je er niet op klikt. Op een telefoon gaat het verder: daar haalt Chrome elke link op die 50 ms in beeld staat, dus wie door een overzicht scrolt, laadt vrijwel elke kaart. Chrome bewaart hooguit twee van zulke prefetches tegelijk en gooit de oudste weg, maar dat begrenst wat hij vasthoudt, niet wat hij ophaalt. Kan je server de extra requests niet goed aan, kies dan moderate: die wacht tot iemand echt even stilhoudt.

public/speculation-rules.json
{
    "prefetch": [
        {
            "source": "document",
            "where": {
                "and": [
                    { "href_matches": "/*" },
                    { "not": { "href_matches": "/*\\?(.+)" } },
                    {
                        "not": {
                            "href_matches": [
                                "/admin{/*}?",
                                "{/*}?/actions/*",
                                "{/*}?/contact{/*}?",
                                "{/*}?/logout{/*}?",
                                "/uploads/*"
                            ]
                        }
                    },
                    { "not": { "selector_matches": "[target=_blank]" } }
                ]
            },
            "eagerness": "eager"
        }
    ]
}

Welke browsers doen mee

Speculation Rules komen uit Chromium. Chrome en Edge ondersteunen prefetch en prerender sinds versie 109; document rules, eagerness en de Speculation-Rules-header kwamen in versie 121. Opera en Samsung Internet draaien op dezelfde engine en doen ook mee. Samen is dat ruim driekwart van alle browsers, volgens caniuse (stand september 2026).

Niet elke Chromium-browser doet mee. Brave leest de regels wel, maar voert ze nooit uit, en heeft geen instelling om dat te veranderen. Firefox ondersteunt de API niet, en Safari heeft hem wel ingebouwd maar standaard uitgeschakeld. Ook in Chrome kan het uitstaan: via de instelling Pagina's vooraf laden, de databesparing of de energiebesparing bij een bijna lege accu.

Dat is geen probleem, want Speculation Rules zijn een hint. Een browser die ze niet kent, slaat ze over en laadt de pagina bij de klik, zoals altijd. Er breekt niets; browsers die meedoen, zijn alleen sneller.

Zo hebben we het op deze site ingebouwd

Je kunt de regels inline in de pagina zetten, in een <script type="speculationrules">. Wij kozen voor een HTTP-header die naar een JSON-bestand in public/ wijst. De Content Security Policy van deze site moet op termijn zonder 'unsafe-inline' kunnen. Een inline regelset zou daar een uitzondering voor nodig hebben; een header niet.

Twee details gaan makkelijk mis. De browser past het bestand alleen toe als het binnenkomt als application/speculationrules+json, en dat type staat niet in de standaardlijst van nginx. En het bestand krijgt Cache-Control: no-cache. Een uitsluiting die je later toevoegt, is meestal een correctheidsfix, en die mag niet een dag in de cache van terugkerende bezoekers blijven hangen. Nginx beantwoordt de controle met een 304, zonder PHP.

Het control panel en de previews van Craft krijgen de header niet. In Live Preview hangt de muis van een redacteur steeds boven links in het voorbeeld. Dat zou alleen requests naar de live pagina's opleveren, zonder dat iemand er iets aan heeft.

.docker/nginx/nginx.conf
# Het control panel en previews krijgen geen regels:
# een lege waarde laat add_header weg.
map $request_uri $speculation_rules_header {
    default '"/speculation-rules.json"';
    ~^/(?:admin|cpresources)(?:[/?]|$) "";
    ~[?&](?:token|x-craft-(?:live-)?preview)= "";
}

server {
    location ~ [^/]\.php(/|$) {
        # ...
        add_header Speculation-Rules $speculation_rules_header always;
    }

    # Alleen dit type past de browser toe.
    location = /speculation-rules.json {
        types { }
        default_type application/speculationrules+json;
        add_header Cache-Control "no-cache";
    }
}

Wat nooit vooruit geladen mag worden

Een prefetch is een gewone GET-request, met de cookies van de bezoeker. Alles wat bij een GET iets verandert of een sessie aanmaakt, moet dus buiten de regels blijven. Bij ons zijn dat:

  • /admin, het control panel;
  • /actions/, de endpoints van Craft;
  • /contact/, omdat het formulier een sessie en een CSRF-token aanmaakt;
  • /logout, omdat Craft je uitlogt bij een gewone GET. Zonder deze regel zou een ingelogde redacteur worden uitgelogd door alleen met de muis over de link te gaan;
  • /uploads/, zodat een hover over een pdf niet het hele bestand ophaalt;
  • links met een query string, en links die in een nieuw tabblad openen.

De patronen zijn URL Patterns, en die hebben hun eigenaardigheden. Een kale * in de query matcht ook een lege query, dus /*\?* sluit elke link uit. Onze regel is daarom /*\?(.+): minstens één teken na het vraagteken. Een test in de repository loopt alle patronen na met de URLPattern uit Node. Hij faalt ook als iemand de URL van de contactpagina verandert zonder de regels aan te passen.

Probeer het hieronder zelf. De eerste knop is een gewone interne link. De tweede gaat naar dezelfde pagina, maar met een query string, en valt dus onder de uitsluiting. In Chrome zie je elke poging terug in DevTools, onder Application → Speculative loads.

Speeltuin

Wijs een van de knoppen aan met je muis, of laat ze op je telefoon even in beeld staan, en klik dan. De testpagina vertelt hoe ze binnenkwam.

Met prefetch

Een gewone interne link. Zodra je muis erop komt, haalt de browser de pagina alvast op.

Zonder prefetch

Dezelfde pagina met een query string. Die sluiten de regels uit, dus hier wacht je op de server.

Waarom nog geen prerender

Prerender zou de klik nog sneller maken, maar dan draait het JavaScript van de volgende pagina al voordat de bezoeker er is. Op deze site hoort daar de cookiebanner bij, en Google Analytics zodra iemand toestemming geeft. Een pagina die vooraf is opgebouwd maar nooit bekeken, mag geen pageview tellen en geen banner laden. Browsers bieden daarvoor document.prerendering en het event prerenderingchange, maar we moeten eerst nagaan hoe Cookiebot en GA4 daarmee omgaan. Tot die tijd houden we het bij prefetch, dat alleen HTML ophaalt en niets uitvoert.