{"id":50993117,"url":"https://github.com/seangeng/intently","last_synced_at":"2026-06-20T05:30:47.200Z","repository":{"id":364564959,"uuid":"1268324976","full_name":"seangeng/intently","owner":"seangeng","description":"Intent-aware prefetching — predicts where the cursor is headed and prefetches/prerenders the link before the click. Zero-config, ~3.5KB.","archived":false,"fork":false,"pushed_at":"2026-06-13T13:54:58.000Z","size":247,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-13T15:24:42.873Z","etag":null,"topics":["instant-page","javascript","prefetch","prerender","quicklink","speculation-rules","typescript","web-performance"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/seangeng.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-13T11:56:17.000Z","updated_at":"2026-06-13T13:55:02.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/seangeng/intently","commit_stats":null,"previous_names":["seangeng/intently"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/seangeng/intently","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seangeng%2Fintently","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seangeng%2Fintently/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seangeng%2Fintently/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seangeng%2Fintently/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/seangeng","download_url":"https://codeload.github.com/seangeng/intently/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seangeng%2Fintently/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34558892,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-20T02:00:06.407Z","response_time":98,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["instant-page","javascript","prefetch","prerender","quicklink","speculation-rules","typescript","web-performance"],"created_at":"2026-06-20T05:30:46.476Z","updated_at":"2026-06-20T05:30:47.191Z","avatar_url":"https://github.com/seangeng.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# intently\n\n**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.\n\n[**intentlyjs.com**](https://intentlyjs.com) · [npm](https://www.npmjs.com/package/intently) · [GitHub](https://github.com/seangeng/intently) · [the writeup](https://seangeng.com/writing/prefetching-on-intent)\n\n```bash\nnpm i intently\n```\n\n## Quick start\n\nOne call binds every eligible same-origin link on the page:\n\n```js\nimport { intently } from \"intently\";\n\nintently();\n```\n\nThat's it. Move toward a link and intently prefetches its destination during the\n~200ms between intent and click, so the navigation feels instant. React:\n\n```tsx\nimport { useIntently } from \"intently/react\";\n\nfunction App() {\n  useIntently();        // once, near the root\n  return \u003cRoutes /\u003e;\n}\n```\n\n## Why another prefetcher\n\nThere are good ones, and intently borrows the best of each:\n\n- [**quicklink**](https://github.com/GoogleChromeLabs/quicklink) prefetches links\n  *in the viewport* when the browser is idle. Great coverage, but it can't tell\n  the one link you want from the fifty you don't.\n- [**instant.page**](https://instant.page) prefetches on *hover* (a 65ms dwell).\n  Precise, but hover is late — the decision is already made by the time you've\n  parked the cursor.\n- [**ForesightJS**](https://github.com/spaansba/ForesightJS) predicts intent from\n  mouse *trajectory* — the right signal — but you register elements yourself and\n  wire up the prefetch.\n\nintently combines them: **zero-config auto-binding** (drop it in, every `\u003ca\u003e` is\ncovered) + **trajectory + proximity prediction** (the link you're aimed at, not\nthe one you happen to be near) + a **tiered loader that uses the Speculation\nRules API** for real prefetch *and* prerender, degrading to `\u003clink rel=prefetch\u003e`\nand then `fetch` where that isn't supported.\n\nThe trajectory idea is old — there are\n[patents on cursor extrapolation](https://patents.google.com/patent/US8566696)\ngoing back years, and ForesightJS does it well today. intently's bet is that\n*prediction + the right modern loader + nothing to configure* is the combination\nworth shipping.\n\n## How it works\n\nThree things, kept small:\n\n**1. Predict.** On every `pointermove`, intently keeps a smoothed velocity. For\neach on-screen link it computes two scores — *proximity* (distance to the link's\nnearest edge, on a squared falloff) and *trajectory* (the dot product of your\nheading with the direction to the link, gated by a forward cone). The higher of\nthe two is the confidence that this link is your next click. (This is the same\nmath as the [input-anticipation](https://seangeng.com/writing/interfaces-that-anticipate-input)\nfocus ring — here it drives a fetch instead of a glow.)\n\n**2. Tier by confidence.** Crossing `intentThreshold` (default `0.5`) prefetches.\nSustained high confidence past `prerenderThreshold` (default `0.85`) upgrades to\na *prerender* — the next page is fully built in a hidden tab, so the click is\ninstant, not just fast. Prerender is expensive, so it stays rare and high-bar.\n\n**3. Load with the best available backend.**\n\n| Tier | Used when | Does |\n|------|-----------|------|\n| Speculation Rules API | Chromium | real `prefetch` **and** `prerender`, cross-document, browser-prioritized |\n| `\u003clink rel=\"prefetch\"\u003e` | most browsers | prefetch only |\n| `fetch()` low priority | last resort | warms the HTTP cache |\n\nOnly in-viewport links are scored (via `IntersectionObserver`), the scoring loop\nsleeps when the pointer is still, and every URL loads once. Offscreen links cost\nnothing.\n\n## Options\n\n```js\nintently({\n  origins: [location.hostname],   // hostnames allowed; or a (url, el) =\u003e boolean\n  ignores: [/\\/logout/, \"?add-to-cart\"], // never touch these (strings or RegExps)\n  signals: [\"trajectory\", \"proximity\", \"hover\", \"touch\"], // which to use\n  intentThreshold: 0.5,           // confidence (0–1) to prefetch\n  prerenderThreshold: 0.85,       // confidence to prerender; false to disable\n  proximityRadius: 80,            // px — how far a link \"notices\" the cursor\n  hoverDelay: 65,                 // ms dwell before hover counts (instant.page's number)\n  eagerOnPress: true,             // prefetch immediately on pointerdown / touchstart\n  viewportPrefetch: false,        // also idle-prefetch in-view links (quicklink-style)\n  limit: Infinity,                // cap total loads\n  respectSaveData: true,          // skip on Save-Data / 2g\n  onPredict: ({ el, url, confidence, signal }) =\u003e {}, // wire a visual affordance\n  onLoad: (url, strategy) =\u003e {},  // \"prefetch\" | \"prerender\"\n});\n```\n\n`intently()` returns a handle:\n\n```js\nconst i = intently();\ni.prefetch(\"/pricing\");   // force it\ni.prerender(\"/checkout\"); // force it (falls back to prefetch where unsupported)\ni.loaded;                 // ReadonlySet\u003cstring\u003e of URLs loaded this session\ni.destroy();              // remove every listener/observer\n```\n\n### Exposed prediction helpers\n\nThe prediction math is exported if you want to build your own affordance (a ring\nthat warms as confidence rises, say):\n\n```js\nimport { distanceToRect, falloff, proximityScore, trajectoryScore } from \"intently\";\n```\n\n## Safety \u0026 taste\n\n- **It only guesses same-origin links** by default, and never touches anything in\n  `ignores`. Put sign-out, \"add to cart\", language switches, and any link with\n  side effects there — especially before enabling prerender, which *runs* the\n  page.\n- **It respects the user.** Save-Data and 2g connections turn it off. A still\n  cursor predicts nothing.\n- **It degrades.** No Speculation Rules → `\u003clink rel=prefetch\u003e` → `fetch`. SSR /\n  no-DOM → a no-op handle.\n- **Prefetch is a hint, not a navigation.** The real click always works whether\n  or not the guess landed.\n\n## When it helps (and when it doesn't)\n\nBiggest wins are **multi-page apps** and **hard navigations** — content sites,\ndocs, e-commerce, blogs — where the next page is a real document fetch. For SPAs\nwith a client router (React Router, Next.js), those frameworks already prefetch\nroute data; intently still helps for outbound and non-router links, and its\nprediction layer is reusable.\n\n## Credits\n\nTrajectory prediction by way of [ForesightJS](https://github.com/spaansba/ForesightJS)\nand a long line of [cursor-extrapolation prior art](https://patents.google.com/patent/US8566696);\nzero-config ergonomics from [instant.page](https://instant.page); viewport idle\nprefetch from [quicklink](https://github.com/GoogleChromeLabs/quicklink); the\nprediction math from my own [input-anticipation](https://seangeng.com/writing/interfaces-that-anticipate-input)\nwork. Built on the [Speculation Rules API](https://developer.mozilla.org/en-US/docs/Web/API/Speculation_Rules_API).\n\nBy [Sean Geng](https://seangeng.com). MIT.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fseangeng%2Fintently","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fseangeng%2Fintently","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fseangeng%2Fintently/lists"}