Skip to content
← writing

Prefetching on intent: a library that loads the page before you click

performance
prefetch
javascript
open-source

A few days ago I wrote about input anticipation: reading the cursor's proximity and trajectory to make a field glow before you touch it. Fun, but mostly cosmetic. Then it nagged at me — the prediction is the valuable part, and lighting up a ring is the least useful thing you can do with it. If I can tell which link you're heading for a beat before you click, I should spend that beat loading the page. So I built intently (npm i intently), a tiny library that prefetches on intent.

Move toward a link below. It warms as your confidence rises, prefetches when you're clearly headed there, and prerenders the one you're committed to.

move toward a link — it warms, prefetches, then prerenders

Prefetchers already exist — and they each miss something

I didn't want to reinvent a solved problem, so first I went and read the good ones. They split by which signal they trust:

  • quicklink prefetches every link that scrolls into the viewport, on idle. Broad and cheap, but it can't tell the one link you want from the forty you don't — it loads all of them.
  • instant.page waits for a hover — a 65ms dwell, after which there's a coin-flip chance you'll click. Precise, but hover is late. By the time the cursor has parked, you'd already decided a quarter-second ago.
  • ForesightJS uses the right signal — mouse trajectory — and does it well. But you register each element yourself and wire the prefetch, and it stops at predicting; it doesn't reach for the newer loading primitives.

The signal I wanted is trajectory, like Foresight: where is the cursor going, not where is it now. The ergonomics I wanted are instant.page's: drop it in, done. And the loader I wanted is the one none of them lean on yet.

The loader is the new part

While I was away from this corner of the web, prefetching got a real browser primitive: the Speculation Rules API. You hand the browser a little JSON rule and it will prefetch a document, or go further and prerender it — fetch the page, run its scripts, build it in a hidden tab — so that clicking is a swap, not a load. It manages priority, backs off under load, and respects the user's data settings.

That unlocks something the older libraries can't express: tiers. Prediction isn't binary. There's "you're drifting this way" and there's "you're committed," and they deserve different responses. So intently maps confidence to strategy:

  • crossing the intent bar (default 0.5) → prefetch the document
  • sustained high confidence (default 0.85) → upgrade to a prerender

Prerender is expensive — it runs the page — so it stays rare and high-bar, for the link you're plainly about to click. Everything degrades: no Speculation Rules → <link rel=prefetch>, no that → a low-priority fetch.

How it predicts

The engine is the input-anticipation one, unchanged. On each pointermove it keeps a smoothed velocity. For every on-screen link it computes two scores — proximity (distance to the link's nearest edge, on a squared falloff) and trajectory (the dot product of your heading with the direction to the link, gated by a forward cone) — and takes the higher as the confidence that this is your next click. Only in-viewport links are scored, the loop sleeps when the cursor is still, and each URL loads once.

The whole thing is one call:

import { intently } from "intently";
 
intently();

That binds every eligible same-origin link on the page. There's a React hook (useIntently()) and the usual knobs — ignores, intentThreshold, prerenderThreshold, onPredict for wiring your own visual affordance — but the zero-argument version is the point.

The honest part

Trajectory-based prefetching is not my idea. There's cursor-extrapolation patent prior art going back more than a decade, academic work on mouse-motion prediction before that, and ForesightJS doing it well right now. I want to be clear about that, because "predict where the mouse is going" can read as novel when it isn't.

What I think is actually worth shipping is the combination: trajectory + proximity prediction, instant.page's nothing-to-configure ergonomics, and the Speculation Rules prerender tier, in one ~4KB drop-in. None of the existing tools sit at that intersection. That's the whole pitch.

Where it helps, and where it doesn't

The wins are real on multi-page sites — docs, blogs, marketing, commerce — where a click is a genuine document navigation. Prerender there feels like teleporting. On a single-page app with a client router, the framework already prefetches route data, so intently earns less; it still helps for outbound and non-router links, and the prediction layer is reusable.

And the sharp edge, worth saying plainly: prefetch is a guess, but prerender is execution. It runs the target page. So anything with a side effect — sign out, add to cart, a language switch, an unsubscribe link — has to be excluded, or you'll fire it just by aiming at it. intently only touches same-origin links by default and takes an ignores list; use it before you raise the prerender threshold. It also stands down on Save-Data and slow connections, because guessing with someone's metered bytes is rude.

The library: npm i intently · live demo + docs · GitHub · the prediction it's built on: input anticipation.