intently
v0.2.1
Published
Intent-aware prefetching. Predicts where the cursor is headed — proximity + trajectory — and prefetches (or prerenders via the Speculation Rules API) the link a beat before the click. Zero-config, ~4KB, framework-agnostic.
Maintainers
Readme
intently
Intent-aware prefetching. Most prefetchers load every link in the viewport, or wait for a hover. intently watches where the cursor is actually heading — proximity and trajectory, the same prediction that powers a focus ring that warms as you approach — and prefetches (or prerenders, via the native Speculation Rules API) the link a beat before you click. Zero-config, ~4KB, framework-agnostic.
intentlyjs.com · npm · GitHub · the writeup
npm i intentlyQuick start
One call binds every eligible same-origin link on the page:
import { intently } from "intently";
intently();That's it. Move toward a link and intently prefetches its destination during the ~200ms between intent and click, so the navigation feels instant. React:
import { useIntently } from "intently/react";
function App() {
useIntently(); // once, near the root
return <Routes />;
}Why another prefetcher
There are good ones, and intently borrows the best of each:
- quicklink prefetches links in the viewport when the browser is idle. Great coverage, but it can't tell the one link you want from the fifty you don't.
- instant.page prefetches on hover (a 65ms dwell). Precise, but hover is late — the decision is already made by the time you've parked the cursor.
- ForesightJS predicts intent from mouse trajectory — the right signal — but you register elements yourself and wire up the prefetch.
intently combines them: zero-config auto-binding (drop it in, every <a> is
covered) + trajectory + proximity prediction (the link you're aimed at, not
the one you happen to be near) + a tiered loader that uses the Speculation
Rules API for real prefetch and prerender, degrading to <link rel=prefetch>
and then fetch where that isn't supported.
The trajectory idea is old — there are patents on cursor extrapolation going back years, and ForesightJS does it well today. intently's bet is that prediction + the right modern loader + nothing to configure is the combination worth shipping.
How it works
Three things, kept small:
1. Predict. On every pointermove, intently keeps a smoothed velocity. For
each 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). The higher of
the two is the confidence that this link is your next click. (This is the same
math as the input-anticipation
focus ring — here it drives a fetch instead of a glow.)
2. Tier by confidence. Crossing intentThreshold (default 0.5) prefetches.
Sustained high confidence past prerenderThreshold (default 0.85) upgrades to
a prerender — the next page is fully built in a hidden tab, so the click is
instant, not just fast. Prerender is expensive, so it stays rare and high-bar.
3. Load with the best available backend.
| Tier | Used when | Does |
|------|-----------|------|
| Speculation Rules API | Chromium | real prefetch and prerender, cross-document, browser-prioritized |
| <link rel="prefetch"> | most browsers | prefetch only |
| fetch() low priority | last resort | warms the HTTP cache |
Only in-viewport links are scored (via IntersectionObserver), the scoring loop
sleeps when the pointer is still, and every URL loads once. Offscreen links cost
nothing.
Options
intently({
origins: [location.hostname], // hostnames allowed; or a (url, el) => boolean
ignores: [/\/logout/, "?add-to-cart"], // never touch these (strings or RegExps)
signals: ["trajectory", "proximity", "hover", "touch"], // which to use
intentThreshold: 0.5, // confidence (0–1) to prefetch
prerenderThreshold: 0.85, // confidence to prerender; false to disable
proximityRadius: 80, // px — how far a link "notices" the cursor
hoverDelay: 65, // ms dwell before hover counts (instant.page's number)
eagerOnPress: true, // prefetch immediately on pointerdown / touchstart
viewportPrefetch: false, // also idle-prefetch in-view links (quicklink-style)
limit: Infinity, // cap total loads
respectSaveData: true, // skip on Save-Data / 2g
onPredict: ({ el, url, confidence, signal }) => {}, // wire a visual affordance
onLoad: (url, strategy) => {}, // "prefetch" | "prerender"
});intently() returns a handle:
const i = intently();
i.prefetch("/pricing"); // force it
i.prerender("/checkout"); // force it (falls back to prefetch where unsupported)
i.loaded; // ReadonlySet<string> of URLs loaded this session
i.destroy(); // remove every listener/observerExposed prediction helpers
The prediction math is exported if you want to build your own affordance (a ring that warms as confidence rises, say):
import { distanceToRect, falloff, proximityScore, trajectoryScore } from "intently";Safety & taste
- It only guesses same-origin links by default, and never touches anything in
ignores. Put sign-out, "add to cart", language switches, and any link with side effects there — especially before enabling prerender, which runs the page. - It respects the user. Save-Data and 2g connections turn it off. A still cursor predicts nothing.
- It degrades. No Speculation Rules →
<link rel=prefetch>→fetch. SSR / no-DOM → a no-op handle. - Prefetch is a hint, not a navigation. The real click always works whether or not the guess landed.
When it helps (and when it doesn't)
Biggest wins are multi-page apps and hard navigations — content sites, docs, e-commerce, blogs — where the next page is a real document fetch. For SPAs with a client router (React Router, Next.js), those frameworks already prefetch route data; intently still helps for outbound and non-router links, and its prediction layer is reusable.
Credits
Trajectory prediction by way of ForesightJS and a long line of cursor-extrapolation prior art; zero-config ergonomics from instant.page; viewport idle prefetch from quicklink; the prediction math from my own input-anticipation work. Built on the Speculation Rules API.
By Sean Geng. MIT.
