Prefetching on intent: a library that loads the page before you click
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.
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.