# ForesightJS > Comprehensive guide to ForesightJS, the most modern way to prefetch your data. Check out llms-full.txt for the full txt and llms.txt for an overview of the docs. - [ForesightJS](/index.md) ## search - [Search the documentation](/search.md) ## docs ### next - [AI Context](/docs/next/ai-context.md): How to use ForesightJS's llms.txt file with AI tools and LLMs - [Behind the Scenes](/docs/next/Behind_the_Scenes.md): A technical deep-dive into the internal workings of ForesightJS, explaining its architecture, how it predicts mouse movements using linear extrapolation and the Liang-Barsky algorithm, and how it predicts tab navigation. - [Element Settings](/docs/next/configuration/element-settings.md): Configure individual element settings when registering with ForesightManager - [Global Settings](/docs/next/configuration/global-settings.md): Configure global ForesightJS settings that apply to the entire ForesightManager instance - [Development Tools](/docs/next/debugging/devtools.md): Documentation on how to use the ForesightJS debugger - [Static Properties](/docs/next/debugging/static-properties.md): Static properties exposed by the Foresight Manager - [Events](/docs/next/events.md): Documentation on how to use the built-in js.foresight events - [Initialize the Manager](/docs/next/getting-started/initialize-the-manager.md): Learn how to initialize the ForesightManager with custom global settings (optional step) - [TypeScript](/docs/next/getting-started/typescript.md): Typescript helpers for the ForesightJS library - [What is ForesightJS](/docs/next/getting-started/what-is-foresightjs.md): Introduction to ForesightJS, an lightweight JavaScript library with full TypeScript support that predicts user intent based on mouse movements and keyboard navigation - [Your First Element](/docs/next/getting-started/your-first-element.md): Learn how to register your first element with ForesightJS and start predicting user intent - [Angular](/docs/next/integrations/angular.md): Integration details to add ForesightJS to your Angular projects - [Next.js](/docs/next/integrations/react/nextjs.md): Integration details to add ForesightJS to your Next.js projects - [React Router](/docs/next/integrations/react/react-router.md): Integration details to add ForesightJS to your React Router projects - [useForesight](/docs/next/integrations/react/useForesight.md): React hook for ForesightJS integration - [Vue](/docs/next/integrations/vue.md): Integration details to add ForesightJS to your Vue projects ### ai-context How to use ForesightJS's llms.txt file with AI tools and LLMs - [AI Context](/docs/ai-context.md): How to use ForesightJS's llms.txt file with AI tools and LLMs ### Behind_the_Scenes A technical deep-dive into the internal workings of ForesightJS, explaining its architecture, how it predicts mouse movements using linear extrapolation and the Liang-Barsky algorithm, and how it predicts tab navigation. - [Behind the Scenes](/docs/Behind_the_Scenes.md): A technical deep-dive into the internal workings of ForesightJS, explaining its architecture, how it predicts mouse movements using linear extrapolation and the Liang-Barsky algorithm, and how it predicts tab navigation. ### configuration - [Element Settings](/docs/configuration/element-settings.md): Configure individual element settings when registering with ForesightManager - [Global Settings](/docs/configuration/global-settings.md): Configure global ForesightJS settings that apply to the entire ForesightManager instance ### debugging - [Development Tools](/docs/debugging/devtools.md): Documentation on how to use the ForesightJS debugger - [Static Properties](/docs/debugging/static-properties.md): Static properties exposed by the Foresight Manager ### events Documentation on how to use the built-in js.foresight events - [Events](/docs/events.md): Documentation on how to use the built-in js.foresight events ### getting-started - [Initialize the Manager](/docs/getting-started/initialize-the-manager.md): Learn how to initialize the ForesightManager with custom global settings (optional step) - [TypeScript](/docs/getting-started/typescript.md): Typescript helpers for the ForesightJS library - [What is ForesightJS](/docs/getting-started/what-is-foresightjs.md): Introduction to ForesightJS, an lightweight JavaScript library with full TypeScript support that predicts user intent based on mouse movements and keyboard navigation - [Your First Element](/docs/getting-started/your-first-element.md): Learn how to register your first element with ForesightJS and start predicting user intent ### integrations - [Angular](/docs/integrations/angular.md): Integration details to add ForesightJS to your Angular projects - [Next.js](/docs/integrations/react/nextjs.md): Integration details to add ForesightJS to your Next.js projects - [React Router](/docs/integrations/react/react-router.md): Integration details to add ForesightJS to your React Router projects - [useForesight](/docs/integrations/react/useForesight.md): React hook for ForesightJS integration - [Vue](/docs/integrations/vue.md): Integration details to add ForesightJS to your Vue projects --- # Full Documentation Content [Skip to main content](#__docusaurus_skipToContent_fallback) [![ForesightJS Logo](/img/logo.svg)![ForesightJS Logo](/img/logo.svg)](/index.md) [**ForesightJS**](/index.md)[Documentation](/docs/getting-started/what-is-foresightjs.md)[Playground](https://foresightjs.com/#playground) [3.3](/docs/getting-started/what-is-foresightjs.md) * [Next](/docs/next/getting-started/what-is-foresightjs.md) * [3.3](/docs/getting-started/what-is-foresightjs.md) [GitHub](https://github.com/spaansba/ForesightJS) Search # Search the documentation Type your search here [](https://www.algolia.com/) Copyright © 2025 ForesightJS, Inc. Built with Docusaurus. --- # AI Context ForesightJS provides an `llms.txt` file following the [llms.txt specification](https://llmstxt.org/) to help LLMs understand and work with the library effectively. ## What is llms.txt?[​](#what-is-llmstxt "Direct link to What is llms.txt?") The `llms.txt` file is a standardized way to provide LLM-friendly information about a project. It contains: * A concise overview of what ForesightJS does * Links to key documentation sections in markdown format * Essential information for understanding the API and usage patterns ## Using ForesightJS's llms.txt[​](#using-foresightjss-llmstxt "Direct link to Using ForesightJS's llms.txt") ### Quick Access[​](#quick-access "Direct link to Quick Access") * **Main file:** [foresightjs.com/llms.txt](https://foresightjs.com/llms.txt) * **Full context:** [foresightjs.com/llms-full.txt](https://foresightjs.com/llms-full.txt) ### Example Prompt[​](#example-prompt "Direct link to Example Prompt") ``` Read the ForesightJS documentation from: https://foresightjs.com/llms.txt I need to implement ForesightJS predictive prefetching in my Next.js application. I want to: 1. Use the useForesight React hook for my custom link components 2. Integrate with Next.js router prefetching using ForesightLink 3. Use the default configuration, except that I want mobile prefetching to be on touch start ``` ## Individual Page Markdown[​](#individual-page-markdown "Direct link to Individual Page Markdown") All documentation pages are available as markdown by adding `.md` to any URL: * `https://foresightjs.com/docs/getting-started/your-first-element.md` * `https://foresightjs.com/docs/configuration/global-settings.md` * `https://foresightjs.com/docs/integrations/react/nextjs.md` --- # Behind the Scenes note Reading this is not necessary to use the library; this is just for understanding how ForesightJS works. ## Architecture[​](#architecture "Direct link to Architecture") ForesightJS uses a singleton pattern with `ForesightManager` as the central instance managing all prediction logic, registered elements, and global settings. Elements are stored in a `Map` where the key is the registered element itself. Multiple registrations of the same element overwrite the existing entry. Since DOM elements can change position and we need to keep the DOM clean, we require `element.getBoundingClientRect()` for each element on each update. To avoid triggering reflows, ForesightJS uses observers: * **`MutationObserver`**: Detects when registered elements are removed from the DOM for automatic unregistration * **`PositionObserver`**: Shopify's library that asynchronously monitors element position changes without polling The `PositionObserver` uses layered observation: 1. `VisibilityObserver` (built on `IntersectionObserver`) determines if elements are viewport-visible 2. `ResizeObserver` tracks size changes of visible target elements 3. Target-specific `IntersectionObserver` instances with smart rootMargin calculations transform viewport observation into target-specific observation regions ## Keyboard & Mouse Users[​](#keyboard--mouse-users "Direct link to Keyboard & Mouse Users") ### Mouse Prediction[​](#mouse-prediction "Direct link to Mouse Prediction") Mouse prediction tracks cursor movement patterns to anticipate click targets by analyzing velocity and trajectory. **Event Handling**: `mousemove` events record `clientX` and `clientY` coordinates. Position history is limited by `positionHistorySize` setting. **Prediction Algorithm**: The `predictNextMousePosition` function implements linear extrapolation: 1. **History Tracking**: Stores past mouse positions with timestamps 2. **Velocity Calculation**: Calculates average velocity using oldest and newest points in history 3. **Extrapolation**: Projects current position along trajectory using calculated velocity and `trajectoryPredictionTimeInMs` setting **Intersection Detection**: The `lineSegmentIntersectsRect` function implements the Liang-Barsky line clipping algorithm to check intersections between the predicted mouse path and element rectangles: 1. Line segment defined by current and predicted mouse positions 2. Target rectangle includes element's `hitSlop` 3. Algorithm clips line segment against rectangle's four edges 4. Intersection confirmed if entry parameter ≤ exit parameter ### Keyboard Prediction[​](#keyboard-prediction "Direct link to Keyboard Prediction") Tab prediction monitors keyboard navigation by detecting Tab key presses and focus changes. **Event Handling**: * `keydown`: Detects Tab key presses * `focusin`: Fires when elements gain focus When `focusin` follows a Tab `keydown`, ForesightJS identifies this as tab navigation. **Tab Navigation Logic**: Uses the `tabbable` library to determine tab order. Results are cached for performance since `tabbable()` calls `getBoundingClientRect()` internally. Cache invalidates on DOM mutations. Prediction process: 1. Identify current focused element's index in tabbable elements list 2. Determine tab direction (forward/backward based on Shift key) 3. Calculate prediction range using current index, direction, and `tabOffset` 4. Trigger callbacks for registered elements within predicted range ### Scroll Prediction[​](#scroll-prediction "Direct link to Scroll Prediction") Leverages existing observer infrastructure without additional event listeners. The `PositionObserver` callback provides elements that have moved. By analyzing the delta between previous and new `boundingClientRect` for viewport-remaining elements, ForesightJS infers scroll direction. Note: This also triggers during animations or dynamic layout changes. ## Touch Device Users[​](#touch-device-users "Direct link to Touch Device Users") Touch devices require different prediction strategies due to the lack of continuous cursor movement. ForesightJS provides two strategies via the `touchDeviceStrategy` prop: **Viewport Entry**: Uses `IntersectionObserver` to detect when elements enter the viewport. Callbacks trigger when elements become visible during scrolling, providing anticipatory loading for content that will soon be in view. **Touch Start**: Listens for `touchstart` events on registered elements. Callbacks fire immediately when users begin touching an element, before the actual `click` event occurs, providing a performance advantage for touch interactions. ## Element Lifecycle[​](#element-lifecycle "Direct link to Element Lifecycle") 1. **Registration**: `ForesightManager.instance.register(element, options)` adds element to internal Map 2. **Callback Execution**: When user intent is detected, element's callback function triggers 3. **Deactivation**: `isCallbackActive` set to `false` after callback execution 4. **Reactivation Wait**: Element remains deactivated for `reactivateAfter` duration (defaults to infinity) 5. **Reactivation**: `isCallbackActive` reset to `true` after duration elapses **Cleanup**: Elements removed via `ForesightManager.instance.unregister(element)` or automatically when DOM removal is detected by `MutationObserver`. --- # Element Settings When registering elements with the `ForesightManager`, you can provide configuration specific to each element. This allows you to fine-tune prediction behavior on a per-element basis. ## Basic Element Registration[​](#basic-element-registration "Direct link to Basic Element Registration") ``` const myElement = document.getElementById("my-element") const { isTouchDevice, isLimitedConnection, isRegistered } = ForesightManager.instance.register({ element: myElement, // Required: The element to monitor callback: () => { // Required: Function that executes when interaction is predicted console.log("prefetching") }, hitSlop: 50, // slop around the element, making its hitbox bigger name: "My Foresight button!", // name visible in the debug tools meta: { route: "/about", }, // your custom meta data for analytics reactivateAfter: 5 * 60 * 1000, // time for the element to reactivate after the callback has been hit }) ``` ## Registration Parameters[​](#registration-parameters "Direct link to Registration Parameters") **TypeScript Type:** `ForesightRegisterOptions` or `ForesightRegisterOptionsWithoutElement` if you want to omit the `element` ### Required Parameters[​](#required-parameters "Direct link to Required Parameters") #### `element`[​](#element "Direct link to element") * **Type:** `element` * **Required:** Yes * **Description:** The DOM element to monitor for user interactions. ``` const button = document.querySelector("#my-button") ForesightManager.instance.register({ element: button, // Any DOM element callback: () => { /* prefetch logic */ }, }) ``` *** #### `callback`[​](#callback "Direct link to callback") * **Type:** `function` * **Required:** Yes * **Description:** Function that executes when interaction is predicted or occurs. This is where your prefetching logic goes. * **Note:** If you await your prefetch logic the `callbackCompleted` [event](/docs/events.md#callbackcompleted) will show you how long your prefetch took to run. ``` ForesightManager.instance.register({ element: myElement, callback: () => { // prefetch logic }, }) ``` *** ### Optional Parameters[​](#optional-parameters "Direct link to Optional Parameters") #### `hitSlop`[​](#hitslop "Direct link to hitslop") * **Type:** `number | Rect` * **Required:** No * **Default:** Uses `defaultHitSlop` from global settings (which is 0 if not set) * **Description:** Fully invisible "slop" around the element that increases the hover hitbox. ``` // Uniform hit slop (20px on all sides) ForesightManager.instance.register({ element: myElement, callback: () => {}, hitSlop: 20, }) // Custom hit slop for each side ForesightManager.instance.register({ element: myElement, callback: () => {}, hitSlop: { top: 10, left: 50, right: 50, bottom: 100, }, }) ``` *** #### `name`[​](#name "Direct link to name") * **Type:** `string` * **Required:** No * **Default:** `element.id` or `"unnamed"` if no id * **Description:** A descriptive name for the element, useful for development tools and debugging. ``` ForesightManager.instance.register({ element: myElement, callback: () => { /* logic */ }, name: "Product Navigation Button", // Helpful for debugging }) ``` *** #### `reactivateAfter`[​](#reactivateafter "Direct link to reactivateafter") * **Type:** `number` * **Required:** No * **Default:** `Infinity` * **Description:** Time in milliseconds after which the callback can be fired again. Set to `Infinity` to prevent callback from firing again after first execution. * **Note:** Even though `ForesightJS` doesn't store any data, you can set this to the same time as your data would become `stale` normally. In most cases you can just leave this on the default value, as it will get reset on page refresh/rerouting anyways. ``` ForesightManager.instance.register({ element: myElement, callback: () => {}, reactivateAfter: 5 * 60 * 1000, // 5 minutes }) ``` *** #### `meta`[​](#meta "Direct link to meta") * **Type:** `Record` * **Required:** No * **Default:** `{}` * **Description:** Stores additional information about the registered element. Visible in all element-related [events](/docs/events.md) and in the [devtools](/docs/debugging/devtools.md). ``` ForesightManager.instance.register({ element: productLink, callback: () => {}, meta: { route: "/about", section: "main", category: "content", }, }) ``` ## Registration Return Value[​](#registration-return-value "Direct link to Registration Return Value") The `register()` method returns useful information about the registration: **TypeScript Type:** `ForesightRegisterResult` ``` const { isTouchDevice, isLimitedConnection, isRegistered } = ForesightManager.instance.register({ element: myElement, callback: () => {}, }) ``` *** ### Return Properties[​](#return-properties "Direct link to Return Properties") #### `isTouchDevice`[​](#istouchdevice "Direct link to istouchdevice") * **Type:** `boolean` * **Description:** Indicates whether the current device is a touch device. *** #### `isLimitedConnection`[​](#islimitedconnection "Direct link to islimitedconnection") * **Type:** `boolean` * **Description:** Is `true` when the user's connection matches the configured `minimumConnectionType` setting (defaults to "3g"). Elements will not be registered when connection is limited. See [Global Settings](/docs/configuration/global-settings.md#minimumconnectiontype) for details. * **Note:** This feature relies on the [Network Information API](https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation) which is not supported in all major browsers. When the API is not available, ForesightJS will register elements regardless of connection speed. *** #### `isRegistered`[​](#isregistered "Direct link to isregistered") * **Type:** `boolean` * **Description:** If `isLimitedConnection` is `true`, this will be `false`. Otherwise indicates successful registration. --- # Global Settings Global settings are specified when initializing the `ForesightManager` and apply to all registered elements. This should be done once at your application's entry point. tip If you want the default global options you don't need to initialize the ForesightManager explicitly. ## Basic Configuration[​](#basic-configuration "Direct link to Basic Configuration") ``` import { ForesightManager } from "js.foresight" // Initialize the manager once at the top of your app if you want custom global settings // ALL SETTINGS ARE OPTIONAL ForesightManager.initialize({ enableMousePrediction: true, positionHistorySize: 8, trajectoryPredictionTime: 80, defaultHitSlop: 10, enableTabPrediction: true, tabOffset: 3, enableScrollPrediction: true, scrollMargin: 150, touchDeviceStrategy: "viewport", enableManagerLogging: false, minimumConnectionType: "3g", }) ``` ## Available Settings[​](#available-settings "Direct link to Available Settings") **TypeScript Type:** `ForesightManagerSettings` note All numeric settings are clamped to their specified Min/Max values to prevent invalid configurations. ### Mouse Prediction Settings[​](#mouse-prediction-settings "Direct link to Mouse Prediction Settings") #### `enableMousePrediction`[​](#enablemouseprediction "Direct link to enablemouseprediction") * **Type:** `boolean` * **Default:** `true` * **Description:** Toggles whether trajectory prediction is active. If `false`, only direct hovers will trigger the callback for mouse users. * **Note:** When this is turned off `ForesightJS` will still trigger `callbacks` while hovering over the element's extended range (`rect` + `hitslop`) ``` ForesightManager.initialize({ enableMousePrediction: false, // Disable trajectory prediction }) ``` *** #### `positionHistorySize`[​](#positionhistorysize "Direct link to positionhistorysize") * **Type:** `number` * **Default:** `8` * **Clamped between:** `2-30` * **Description:** Number of mouse positions to keep in history for velocity calculations. Higher values provide smoother predictions but use more memory. ``` ForesightManager.initialize({ positionHistorySize: 12, // Keep more position history for smoother predictions }) ``` *** #### `trajectoryPredictionTime`[​](#trajectorypredictiontime "Direct link to trajectorypredictiontime") * **Type:** `number` * **Default:** `120` * **Clamped between:** `10-200` milliseconds * **Description:** How far ahead (in milliseconds) to predict the mouse trajectory. Larger values trigger callbacks sooner but may reduce accuracy. ``` ForesightManager.initialize({ trajectoryPredictionTime: 100, // Predict 100ms into the future }) ``` *** ### Keyboard Prediction Settings[​](#keyboard-prediction-settings "Direct link to Keyboard Prediction Settings") #### `enableTabPrediction`[​](#enabletabprediction "Direct link to enabletabprediction") * **Type:** `boolean` * **Default:** `true` * **Description:** Toggles whether keyboard prediction is active for tab navigation. ``` ForesightManager.initialize({ enableTabPrediction: false, // Disable keyboard prediction }) ``` *** #### `tabOffset`[​](#taboffset "Direct link to taboffset") * **Type:** `number` * **Default:** `2` * **Clamped between:** `0-20` * **Description:** Number of tab stops away from an element to trigger its callback. Only works when `enableTabPrediction` is `true`. ``` ForesightManager.initialize({ tabOffset: 1, // Trigger callback when 1 tab stop away }) ``` *** ### Scroll Prediction Settings[​](#scroll-prediction-settings "Direct link to Scroll Prediction Settings") #### `enableScrollPrediction`[​](#enablescrollprediction "Direct link to enablescrollprediction") * **Type:** `boolean` * **Default:** `true` * **Description:** Toggles whether scroll prediction is active. ``` ForesightManager.initialize({ enableScrollPrediction: false, // Disable scroll prediction }) ``` *** #### `scrollMargin`[​](#scrollmargin "Direct link to scrollmargin") * **Type:** `number` * **Default:** `150` * **Clamped between:** `30-300` pixels * **Description:** Sets the pixel distance to check from the mouse position in the scroll direction for triggering callbacks. ``` ForesightManager.initialize({ scrollMargin: 200, // Check 200px ahead in scroll direction }) ``` *** ### Hit Slop Settings[​](#hit-slop-settings "Direct link to Hit Slop Settings") #### `defaultHitSlop`[​](#defaulthitslop "Direct link to defaulthitslop") * **Type:** `number | Rect` * **Default:** `{top: 0, left: 0, right: 0, bottom: 0}` * **Clamped between:** `0-2000` pixels (per side) * **Description:** Default fully invisible "slop" around elements for all registered elements. Basically increases the hover hitbox. * **Note:** This can be overwritten on an element basis by setting its `hitslop`. ``` // Uniform hit slop ForesightManager.initialize({ defaultHitSlop: 20, // 20px on all sides }) // Custom hit slop for each side ForesightManager.initialize({ defaultHitSlop: { top: 10, left: 20, right: 20, bottom: 15, }, }) ``` *** ### Touch Device Settings (v3.3.0+)[​](#touch-device-settings-v330 "Direct link to Touch Device Settings (v3.3.0+)") #### `touchDeviceStrategy`[​](#touchdevicestrategy "Direct link to touchdevicestrategy") * **Type:** `TouchDeviceStrategy` * **Default:** `"onTouchStart"` * **Options:** `"none"`, `"viewport"`, `"onTouchStart"` * **Description:** Strategy to use for touch devices (mobile / pen users). ``` ForesightManager.initialize({ touchDeviceStrategy: "viewport", }) ``` *** ### Minimum Connection Settings[​](#minimum-connection-settings "Direct link to Minimum Connection Settings") #### `minimumConnectionType`[​](#minimumconnectiontype "Direct link to minimumconnectiontype") * **Type:** `MinimumConnectionType` * **Default:** `"3g"` * **Options:** `"slow-2g"`, `"2g"`, `"3g"`, `"4g"` * **Description:** The minimum connection speed required to register elements. Elements will not be registered when the user's connection is slower than this threshold. * **Note:** This feature relies on the [Network Information API](https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation) which is not supported in all major browsers. When the API is not available, ForesightJS will register elements regardless of connection speed. ``` ForesightManager.initialize({ minimumConnectionType: "3g", }) ``` *** ### Other Settings[​](#other-settings "Direct link to Other Settings") #### `enableManagerLogging`[​](#enablemanagerlogging "Direct link to enablemanagerlogging") * **Type:** `boolean` * **Default:** `"false"` * **Description:** Logs basic information about the `ForesightManager` and its handlers that is not available through [events](/docs/events.md). Mostly used by the maintainers of `ForesightJS` to debug the manager, but might be useful for implementers aswell. * **Note:** Examples of logs are: Initializing the manager, switching from device strategy (e.g. Mouse to pen), aborting controllers and invalidating cache. ``` ForesightManager.initialize({ enableManagerLogging: true, }) ``` ## Runtime Configuration Changes[​](#runtime-configuration-changes "Direct link to Runtime Configuration Changes") You can update global settings at runtime using `alterGlobalSettings()`. This is not adviced for regular use and is mainly used for developers creating tools on top of `Foresight`. For regular use, you should set global settings during initialization with `ForesightManager.initialize()`. ``` // Change settings after initialization ForesightManager.instance.alterGlobalSettings({ trajectoryPredictionTime: 100, enableScrollPrediction: false, }) ``` This is particularly useful when integrating with development tools or when you need to adjust settings based on user preferences or device capabilities. ## Development Tools Integration[​](#development-tools-integration "Direct link to Development Tools Integration") Development Tools Visual development tools are available as a separate package. See the [development tools documentation](/docs/debugging/devtools.md) for details on installing and configuring the `js.foresight-devtools` package. The development tools provide a real-time interface for adjusting these global settings and seeing their immediate effects on prediction behavior. --- # Development Tools [![npm version](https://img.shields.io/npm/v/js.foresight-devtools.svg)](https://www.npmjs.com/package/js.foresight-devtools) [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) `ForesightJS` offers dedicated [Development Tools](https://github.com/spaansba/ForesightJS/tree/main/packages/js.foresight-devtools), written in [Lit](https://lit.dev/), to help you better understand and fine-tune how `ForesightJS` works within your application. You can see the development tools in action on the [playground page](https://foresightjs.com/#playground), which includes visual trajectory indicators, element boundaries, and a control panel in the bottom-right corner. These tools are built entirely using `ForesightJS`'s [built-in events](/docs/events.md), demonstrating how you can create your own monitoring and debugging tools using the same event system. ## Installation[​](#installation "Direct link to Installation") To install the `ForesightJS` Development Tools package, use your preferred package manager: ``` pnpm add -D js.foresight-devtools # or npm install -D js.foresight-devtools # or yarn add -D js.foresight-devtools ``` ## Enabling Development Tools[​](#enabling-development-tools "Direct link to Enabling Development Tools") ``` import { ForesightManager } from "js.foresight" import { ForesightDevtools } from "js.foresight-devtools" // Initialize ForesightJS ForesightManager.initialize({}) // Initialize the development tools (all options are optional) ForesightDevtools.initialize({ showDebugger: true, isControlPanelDefaultMinimized: false, // optional setting which allows you to minimize the control panel on default showNameTags: true, // optional setting which shows the name of the element sortElementList: "visibility", // optional setting for how the elements in the control panel are sorted logging: { logLocation: "controlPanel", // Where to log the Foresight Events callbackCompleted: true, elementReactivated: true, callbackInvoked: true, elementDataUpdated: false, elementRegistered: false, elementUnregistered: false, managerSettingsChanged: true, mouseTrajectoryUpdate: false, // dont log this to the devtools scrollTrajectoryUpdate: false, // dont log this to the devtools deviceStrategyChanged: true, }, }) ``` ## Development Tools Features[​](#development-tools-features "Direct link to Development Tools Features") Once enabled, the `ForesightJS` Development Tools add several visual layers to your application, including mouse and scroll trajectories and element hitboxes. A control panel also appears in the bottom-right corner of the screen. ### Control Panel[​](#control-panel "Direct link to Control Panel") The control panel provides three main tabs for debugging and configuration. Each tab serves a specific purpose in understanding and tuning ForesightJS behavior. #### Settings Tab[​](#settings-tab "Direct link to Settings Tab") The Settings tab provides real-time controls for all [Global Configurations](/docs/configuration/global-settings.md). Changes made through these controls immediately affect the `ForesightManager` configuration, allowing you to see how different settings impact your app without fiddling in your code. #### Elements Tab[​](#elements-tab "Direct link to Elements Tab") The Elements tab displays a overview of all currently registered elements within the `ForesightManage`r. Each element entry shows its current status through color-coded indicators: * 🟢 **Green** - Active visible elements in desktop mode * ⚫ **Grey** - Active invisible elements in desktop mode * 🟣 **Purple** - Active elements while in touch device mode (all elements, we dont track visibility in this mode) * 🟡 **Yellow** - Elements which callbacks are currently executing * 🔘 **Light Gray** - Inactive elements Each element can also be expanded to reveal its [`ForesightElementData`](/docs/next/getting-started/typescript.md#foresightelementdata) information including settings, callback status, and metadata. A countdown timer appears for elements in their reactivation cooldown period (`reactivateAfter`), clicking this timer will instantly reactivate the element. #### Log Tab[​](#log-tab "Direct link to Log Tab") The Log tab displays real-time [events](/docs/events.md) emitted by `ForesightJS`. You can see callback execution times, the full element's lifecycle and other system events. Events can be filtered through the devtools initialization configuration or in the control panel itself. You can also print out the complete [`ForesightManager.instance.getManagerData`](/docs/debugging/static-properties.md#foresightmanagerinstancegetmanagerdata) state without having to call it from your code. caution Avoid logging frequently emitted events to the browser console, as it can noticeably slow down your development environment. Use the control panel for this instead. note Element overlay visualization and visibility sorting in the control panel only work with desktop/mouse prediction strategies. When debugging `touchDeviceStrategy` configurations, these features are not available as touch strategies don't track the same positioning data. --- # Static Properties The ForesightManager exposes several static properties for accessing and checking the manager state. ***All properties are read-only*** ## ForesightManager.instance[​](#foresightmanagerinstance "Direct link to ForesightManager.instance") Gets the singleton instance of ForesightManager, initializing it if necessary. This is the primary way to access the manager throughout your application. **Returns:** `ForesightManager` **Example:** ``` const manager = ForesightManager.instance // Register an element manager.register({ element: myButton, callback: () => console.log("Predicted interaction!"), }) // or ForesightManager.instance.register({ element: myButton, callback: () => console.log("Predicted interaction!"), }) ``` ## ForesightManager.instance.registeredElements[​](#foresightmanagerinstanceregisteredelements "Direct link to ForesightManager.instance.registeredElements") Gets a Map of all currently registered elements and their associated data. This is useful for debugging or inspecting the current state of registered elements. **Returns:** `ReadonlyMap` ## ForesightManager.instance.isInitiated[​](#foresightmanagerinstanceisinitiated "Direct link to ForesightManager.instance.isInitiated") Checks whether the ForesightManager has been initialized. **Returns:** `Readonly` ## ForesightManager.instance.getManagerData[​](#foresightmanagerinstancegetmanagerdata "Direct link to ForesightManager.instance.getManagerData") Snapshot of the current `ForesightManager` state, including all [global settings](/docs/configuration/global-settings.md), registered elements, position observer data, and interaction statistics. This is primarily used for debugging, monitoring, and development purposes. **Properties:** * `registeredElements` - `Map` of all currently registered elements and their associated data * `eventListeners` - `Map` of all event listeners listening to [ForesightManager Events](/docs/events.md). * `globalSettings` - Current [global configuration](/docs/configuration/global-settings.md) settings * `globalCallbackHits` - Total `callback` execution counts by interaction type (mouse/tab/scroll/viewport/touch) and by subtype (hover/trajctory for mouse, forwards/reverse for tab, direction for scroll) * `currentDeviceStrategy` - Which strategy is being used. Can be either `touch` or `mouse`, this changes dynamically * `activeElementCount` - Amount of elements currently active (not the same as registered) **Returns:** `Readonly` The return will look something like this: ``` { "registeredElements": { "size": 7, "entries": "" }, "activeElementCount": 5, "currentDeviceStrategy": "mouse", "eventListeners": { "0": { "elementRegistered": [] }, "1": { "elementUnregistered": [] }, "2": { "elementDataUpdated": [] }, "3": { "mouseTrajectoryUpdate": [] }, "4": { "scrollTrajectoryUpdate": [] }, "5": { "managerSettingsChanged": [] }, "6": { "callbackFired": [] } }, "globalSettings": { "defaultHitSlop": { "bottom": 10, "left": 10, "right": 10, "top": 10 }, "enableMousePrediction": true, "enableScrollPrediction": true, "enableTabPrediction": true, "positionHistorySize": 10, "resizeScrollThrottleDelay": 0, "scrollMargin": 150, "tabOffset": 2, "trajectoryPredictionTime": 100 }, "globalCallbackHits": { "mouse": { "hover": 0, "trajectory": 3 }, "scroll": { "down": 2, "left": 0, "right": 0, "up": 0 }, "tab": { "forwards": 3, "reverse": 0 }, "touch": 0, "viewport": 0, "total": 8 } } ``` --- # Events ForesightManager emits various events during to provide insight into element registration, prediction activities, and callback executions. These events are primarily used by the [ForesightJS DevTools](/docs/debugging/devtools.md) for visual debugging and monitoring, but can also be leveraged for telemetry, analytics, and performance monitoring in your applications. ## Usage[​](#usage "Direct link to Usage") All events are visible in the logs tab of the [devtools](/docs/debugging/devtools.md). However for tracking/analytics in production, implementing them in your own code is straightforward with the standard `addEventListener` pattern. ``` import { ForesightManager } from "js.foresight" // Define handler as const for removal const handleCallbackInvoked = event => { console.log( `Callback executed for ${event.elementData.name} in ${event.hitType.kind} mode, which took ${event.elapsed} ms` ) } // Add the event ForesightManager.instance.addEventListener("callbackInvoked", handleCallbackInvoked) // Later, remove the listener using the same reference ForesightManager.instance.removeEventListener("callbackInvoked", handleCallbackInvoked) ``` ### AbortController support[​](#abortcontroller-support "Direct link to AbortController support") Event listeners support [AbortController signals](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) for easy cleanup. ``` const controller = new AbortController() manager.addEventListener("callbackInvoked", handleCallbackInvoked, { signal: controller.signal }) controller.abort() ``` ## Available Events[​](#available-events "Direct link to Available Events") ### Interaction Events[​](#interaction-events "Direct link to Interaction Events") Events fired when user interactions trigger callbacks. *** #### `callbackInvoked`[​](#callbackinvoked "Direct link to callbackinvoked") Fired **before** an element's callback is executed ``` type CallbackInvokedEvent = { type: "callbackInvoked" timestamp: number elementData: ForesightElementData hitType: HitType } ``` **Related Types:** [`CallbackHitType`](/docs/getting-started/typescript.md#callbackhittype) • [`ForesightElementData`](/docs/getting-started/typescript.md#foresightelementdata) *** #### `callbackCompleted`[​](#callbackcompleted "Direct link to callbackcompleted") Fired **after** an element's callback is executed ``` type CallbackCompletedEvent = { type: "callbackCompleted" timestamp: number elementData: ForesightElementData hitType: CallbackHitType elapsed: number // Time between callbackInvoked and callbackCompleted status: "success" | "error" | undefined errorMessage: string | undefined | null wasLastActiveElement: boolean } ``` **Related Types:** [`CallbackHitType`](/docs/getting-started/typescript.md#callbackhittype) • [`ForesightElementData`](/docs/getting-started/typescript.md#foresightelementdata) ### Element Lifecycle Events[​](#element-lifecycle-events "Direct link to Element Lifecycle Events") Events fired during element registration, updates, and cleanup. *** #### `elementRegistered`[​](#elementregistered "Direct link to elementregistered") Fired when an element is successfully registered with `ForesightManager` using `foresightmanager.instance.register(element)`. ``` type ElementRegisteredEvent = { type: "elementRegistered" timestamp: number elementData: ForesightElementData } ``` **Related Types:** [`ForesightElementData`](/docs/getting-started/typescript.md#foresightelementdata) *** #### `elementReactivated`[​](#elementreactivated "Direct link to elementreactivated") Fired when an element is reactivated after its callback was triggered. This happends after `reactivateAfter` ms (default infinity) or with `foresightmanager.instance.reactivate(element)`. ``` type ElementReactivatedEvent = { type: "elementReactivated" timestamp: number elementData: ForesightElementData } ``` **Related Types:** [`ForesightElementData`](/docs/getting-started/typescript.md#foresightelementdata) *** #### `elementUnregistered`[​](#elementunregistered "Direct link to elementunregistered") Fired when an element is removed from `ForesightManager`'s tracking. This only happends when the element is removed from the `DOM` or via developer actions like `foresightmanar.instance.unregister(element)` ``` type ElementUnregisteredEvent = { type: "elementUnregistered" timestamp: number elementData: ForesightElementData unregisterReason: "disconnected" | "apiCall" | "devtools" | (string & {}) wasLastRegisteredElement: boolean } ``` **Related Types:** [`ForesightElementData`](/docs/getting-started/typescript.md#foresightelementdata) *** #### `elementDataUpdated`[​](#elementdataupdated "Direct link to elementdataupdated") Fired when tracked element data changes (bounds/visibility only). Does not fire on any updates regarding `callback` data. ``` type ElementDataUpdatedEvent = { type: "elementDataUpdated" elementData: ForesightElementData updatedProps: UpdatedDataPropertyNames[] // "bounds" | "visibility" } ``` **Related Types:** [`ForesightElementData`](/docs/getting-started/typescript.md#foresightelementdata) ### Prediction Events[​](#prediction-events "Direct link to Prediction Events") Events fired during movement prediction calculations. *** #### `mouseTrajectoryUpdate`[​](#mousetrajectoryupdate "Direct link to mousetrajectoryupdate") Fired during mouse movement for trajectory calculations ``` type MouseTrajectoryUpdateEvent = { type: "mouseTrajectoryUpdate" trajectoryPositions: { currentPoint: { x: number; y: number } predictedPoint: { x: number; y: number } } predictionEnabled: boolean } ``` *** #### `scrollTrajectoryUpdate`[​](#scrolltrajectoryupdate "Direct link to scrolltrajectoryupdate") Fired during scroll events when scroll prediction is active ``` type ScrollTrajectoryUpdateEvent = { type: "scrollTrajectoryUpdate" currentPoint: Point // { x: number; y: number } predictedPoint: Point // { x: number; y: number } scrollDirection: ScrollDirection // "down" | "up" | "left" | "right" } ``` *** ### Configuration Events[​](#configuration-events "Direct link to Configuration Events") Events fired when ForesightManager configuration changes. *** #### `managerSettingsChanged`[​](#managersettingschanged "Direct link to managersettingschanged") Fired when [global](/docs/configuration/global-settings.md) `ForesightManager` settings are updated via the [devtools](/docs/debugging/devtools.md) or via `foresightmanager.instance.alterGlobalSettings()`. ``` type ManagerSettingsChangedEvent = { type: "managerSettingsChanged" timestamp: number managerData: Readonly updatedSettings: UpdatedManagerSetting[] } ``` #### Extra Type Info[​](#extra-type-info "Direct link to Extra Type Info") [`ForesightElementData`](/docs/getting-started/typescript.md#foresightelementdata) *** #### `deviceStrategyChanged`[​](#devicestrategychanged "Direct link to devicestrategychanged") Fired when user switches from mouse+keyboard setup to pen/touch or vice versa. ``` type ManagerSettingsChangedEvent = { type: "deviceStrategyChanged" timestamp: number newStrategy: CurrentDeviceStrategy oldStrategy: CurrentDeviceStrategy } ``` *** --- # Initialize the Manager Optional Step This step is **not needed** if you don't want to configure ForesightJS. The manager will initialize automatically with default settings when you register your first element. If you don't want to configure anything, skip to [Your First Element](/docs/getting-started/your-first-element.md). If you want to customize ForesightJS's global behavior, you can initialize the manager with your preferred settings at your application's entry point. ## Optional Settings[​](#optional-settings "Direct link to Optional Settings") You can configure any of these [global settings](/docs/configuration/global-settings.md). ``` ForesightManager.initialize({ // Mouse prediction settings enableMousePrediction: true, // Enable/disable mouse trajectory prediction trajectoryPredictionTime: 120, // How far ahead to predict (10-200ms) positionHistorySize: 8, // Mouse positions to track (2-30) // Keyboard prediction settings enableTabPrediction: true, // Enable/disable keyboard navigation prediction tabOffset: 2, // Tab stops ahead to trigger (0-20) // Scroll prediction settings enableScrollPrediction: true, // Enable/disable scroll prediction scrollMargin: 150, // Pixel distance for scroll detection (30-300) // Touch device settings touchDeviceStrategy: "viewport", // "none", "viewport", or "onTouchStart" // Connection settings minimumConnectionType: "3g", // "slow-2g", "2g", "3g", "4g" // Default element settings defaultHitSlop: 0, // Default hit slop for all elements (number or {top, right, bottom, left}) }) ``` ## Good to know[​](#good-to-know "Direct link to Good to know") The `ForesightManager` is a singleton, meaning it can only be initialized once. If you call `ForesightManager.initialize()` multiple times, only the first call will take effect. Any subsequent calls will simply return the already initialized instance. The manager also provides several static methods you can use to get information about which elements are registered, how many callbacks have been ran and all the listeneing [event](/docs/events.md) listeners. You can find a list of these methods [here](/docs/debugging/static-properties.md). --- # TypeScript ForesightJS is fully written in `TypeScript` to make sure your development experience is as good as possbile. ## Most Used Internal types[​](#most-used-internal-types "Direct link to Most Used Internal types") ### ForesightElementData[​](#foresightelementdata "Direct link to ForesightElementData") This is the main type used in ForesightJS as it gathers all information about a registered element. `ForesightElementData` is returned in most [events](/docs/events.md) as `elementData` ``` type ForesightElementData = { callback: ForesightCallback // () => void name: string id: string // only for internals elementBounds: { originalRect: DOMRectReadOnly hitSlop: Exclude expandedRect: Readonly // originalRect + hitSlop } trajectoryHitData: TrajectoryHitData // only for internals isIntersectingWithViewport: boolean element: ForesightElement registerCount: number //Amount of times this element has been (re)registered. meta: Record callbackInfo: { callbackFiredCount: number lastCallbackInvokedAt: number | undefined lastCallbackCompletedAt: number | undefined lastCallbackRuntime: number | undefined lastCallbackStatus: callbackStatus lastCallbackErrorMessage: string | undefined | null reactivateAfter: number isCallbackActive: boolean isRunningCallback: boolean reactivateTimeoutId?: ReturnType } } ``` ### CallbackHitType[​](#callbackhittype "Direct link to CallbackHitType") ``` type CallbackHitType = | { kind: "mouse"; subType: "hover" | "trajectory" } | { kind: "tab"; subType: "forwards" | "reverse" } | { kind: "scroll"; subType: "up" | "down" | "left" | "right" } ``` ### ForesightManagerData[​](#foresightmanagerdata "Direct link to ForesightManagerData") Snapshot of the current ForesightManager state, including all [global settings](/docs/configuration/global-settings.md), registered elements, position observer data, and interaction statistics. This is primarily used for debugging, monitoring, and development purposes. This data is returned in the [`managerSettingsChanged`](/docs/events.md) event or by calling [`ForesightManager.instance.getManagerData`](/docs/debugging/static-properties.md#foresightmanagerinstancegetmanagerdata) manually. ``` type ForesightManagerData = { registeredElements: ReadonlyMap // See above for ForesightElementData globalSettings: Readonly // See configuration for global settings globalCallbackHits: Readonly // See above for CallbackHitType, this is that but all of them combined eventListeners: ReadonlyMap // All event listeners currently listening } ``` ## Helper Types[​](#helper-types "Direct link to Helper Types") ### ForesightRegisterOptionsWithoutElement[​](#foresightregisteroptionswithoutelement "Direct link to ForesightRegisterOptionsWithoutElement") Usefull for if you want to create a custom button component in a modern framework (for example React). And you want to have the `ForesightRegisterOptions` used in `ForesightManager.instance.register({})` without the element as the element will be the ref of the component. This type is used in the [`useForesight`](/docs/integrations/react/useForesight.md) hook for React. ``` type ForesightButtonProps = { registerOptions: ForesightRegisterOptionsWithoutElement } ``` --- # What is ForesightJS [![npm version](https://img.shields.io/npm/v/js.foresight.svg)](https://www.npmjs.com/package/js.foresight) [![npm downloads](https://img.shields.io/npm/dt/js.foresight.svg)](https://www.npmjs.com/package/js.foresight) [![Bundle Size](https://img.shields.io/bundlephobia/minzip/js.foresight)](https://bundlephobia.com/package/js.foresight) [![GitHub last commit](https://img.shields.io/github/last-commit/spaansba/ForesightJS)](https://github.com/spaansba/ForesightJS/commits) [![GitHub stars](https://img.shields.io/github/stars/spaansba/ForesightJS.svg?style=social\&label=Star)](https://github.com/spaansba/ForesightJS) [![Best of JS](https://img.shields.io/endpoint?url=https://bestofjs-serverless.now.sh/api/project-badge?fullName=spaansba%2FForesightJS%26since=daily)](https://bestofjs.org/projects/foresightjs) [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Demo](https://img.shields.io/badge/demo-live-blue)](https://foresightjs.com#playground) ForesightJS is a lightweight JavaScript library that predicts user intent to prefetch content before it's needed. **It works completely out of the box without configuration**, supporting both desktop and mobile devices with different prediction strategies. ## Understanding ForesightJS's Role[​](#understanding-foresightjss-role "Direct link to Understanding ForesightJS's Role") When you over simplify prefetching it exists of three parts: * **What** resource or data to load * **How** the loading method and caching strategy is * **When** the optimal moment to start fetching is ForesightJS takes care of the **When** by predicting user intent with mouse trajectory and tab navigation. You supply the **What** and **How** inside your `callback` when you register an element. ## Download[​](#download "Direct link to Download") ``` pnpm add js.foresight # or npm install js.foresight # or yarn add js.foresight ``` ## Prediction Strategies[​](#prediction-strategies "Direct link to Prediction Strategies") ForesightJS uses different prediction strategies depending on the device type. For limited connections (slower than the minimum connection type - default: 3G - or [data-saver mode](https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/saveData)), we respect the user's preference to minimize data usage and skip registration. Foresight automatically detects mouse, pen, or touch input, and updates its strategy if the input method changes. ### Keyboard/Mouse Users[​](#keyboardmouse-users "Direct link to Keyboard/Mouse Users") Pick and choose multiple prediction strategies. All are enabled by default, but you can disable any of them when [initializing](/docs/getting-started/initialize-the-manager.md) the `ForesightManager`. * **Mouse Trajectory** default - Analyzes cursor movement patterns to predict which links users are heading towards and prefetches content before they arrive * **Keyboard Navigation** default - Tracks tab key usage to prefetch when the user is N tab stops away from your registered element * **Scroll** default - Prefetches content when users scroll towards registered elements, predicting which elements will be reached based on scroll direction ### Touch Devices (v3.3.0+)[​](#touch-devices-v330 "Direct link to Touch Devices (v3.3.0+)") While desktop systems use multiple concurrent prediction strategies, touch devices operate with a single active strategy that can be configured while [initializing](/docs/getting-started/initialize-the-manager.md) the `ForesightManager`. * **onTouchStart** default - Captures the initial touch event to begin prefetching when users start interacting with registered elements * **Viewport Enter** - Detects when registered elements enter the viewport and prefetches their content based on scroll behavior and visibility * **None** - Disables ForesightJS on touch devices (previous behavior) ## Why ForesightJS?[​](#why-foresightjs "Direct link to Why ForesightJS?") ForesightJS is designed for developers who want to squeeze every drop of performance out of their web applications. It's a specialized tool, not something to drop into every project. ### Which problems does ForesightJS solve?[​](#which-problems-does-foresightjs-solve "Direct link to Which problems does ForesightJS solve?") #### Problem 1: On-Hover Prefetching Still Has Latency[​](#problem-1-on-hover-prefetching-still-has-latency "Direct link to Problem 1: On-Hover Prefetching Still Has Latency") Traditional hover-based prefetching only triggers after the user's cursor reaches an element. This approach wastes the critical 100-200ms window between when a user begins moving toward a target and when the hover event actually fires—time that could be used for prefetching. #### Problem 2: Viewport-Based Prefetching is Wasteful[​](#problem-2-viewport-based-prefetching-is-wasteful "Direct link to Problem 2: Viewport-Based Prefetching is Wasteful") Many modern frameworks (like Next.js) automatically prefetch resources for all links that enter the viewport. While well-intentioned, this creates significant overhead since users typically interact with only a small fraction of visible elements. Simply scrolling up and down the Next.js homepage can trigger ***1.59MB*** of unnecessary prefetch requests. #### Problem 3: Hover-Based Prefetching Excludes Keyboard Users[​](#problem-3-hover-based-prefetching-excludes-keyboard-users "Direct link to Problem 3: Hover-Based Prefetching Excludes Keyboard Users") Many routers rely on hover-based prefetching, but this approach completely excludes keyboard users since keyboard navigation never triggers hover events. This means keyboard users miss out on the performance benefits that mouse users get from hover-based prefetching. --- # Your First Element This guide will walk you through registering your first element with `ForesightJS` and understanding how the prediction system works. ## Basic Usage Example[​](#basic-usage-example "Direct link to Basic Usage Example") This basic example is in vanilla JS, ofcourse most people will use ForesightJS with a framework. You can read about framework integrations below. ``` import { ForesightManager } from "js.foresight" // Register an element to be tracked const myLink = document.getElementById("my-link") ForesightManager.instance.register({ element: myLink, callback: () => { // This is where your prefetching logic goes console.log("User is likely to interact with this element!") }, // Optional element settings }) ``` Thats it! ## Provide element settings[​](#provide-element-settings "Direct link to Provide element settings") However if you want to add a bit more power to your element you can give it the following props: ``` import { ForesightManager } from "js.foresight" const myLink = document.getElementById("my-link") ForesightManager.instance.register({ element: myLink, callback: () => { console.log("User is likely to interact with this element!") }, hitSlop: 50, // slop around the element, making its hitbox bigger name: "My Foresight button!", // name visible in the debug tools meta: { route: "/about", }, // your custom meta data for analytics reactivateAfter: 5 * 60 * 1000, // time for the element to reactivate after the callback has been hit }) ``` ## Integrations[​](#integrations "Direct link to Integrations") Since ForesightJS is framework agnostic, it can be integrated with any JavaScript framework. Ready-to-use implementations are available for: * [Next.js](/docs/integrations/react/nextjs.md) * [React Router](/docs/integrations/react/react-router.md) * [Angular](/docs/integrations/angular.md) ## Development Tools[​](#development-tools "Direct link to Development Tools") ForesightJS has dedicated [Development Tools](/docs/debugging/devtools.md) that help you understand and tune how prediction is working in your application: ``` npm install js.foresight-devtools ``` ``` import { ForesightDevtools } from "js.foresight-devtools" // Initialize development tools ForesightDevtools.initialize({ // optional props }) ``` --- # Angular ``` npm install js.foresight ngx-foresight npm install -D js.foresight-devtools # or pnpm add js.foresight js.foresight-devtools ngx-foresight pnpm add -D js.foresight-devtools ``` After that import the `ForesightjsDirective` to the components with `href` and `routerLink`, and use the `ForesightjsStrategy` as `preloadingStrategy` in the router's configuration. For example: ``` import { ForesightManager } from 'js.foresight'; import { ForesightDevtools } from 'js.foresight-devtools'; import { ForesightjsDirective } from 'ngx-foresight'; ForesightManager.initialize({ enableMousePrediction: true, positionHistorySize: 8, trajectoryPredictionTime: 80, defaultHitSlop: 10, enableTabPrediction: true, tabOffset: 3, enableScrollPrediction: true, scrollMargin: 150, }); ForesightDevtools.initialize({ showDebugger: true, isControlPanelDefaultMinimized: true, // optional setting which allows you to minimize the control panel on default showNameTags: true, // optional setting which shows the name of the element sortElementList: 'visibility', // optional setting for how the elements in the control panel are sorted }); ``` ``` ``` ``` // configure preloading strategy as per routes provideRouter(routes, withPreloading(ForesightjsStrategy)), // for older versions RouterModule.forRoot(routes, { preloadingStrategy: ForesightjsStrategy }) ``` --- # Next.js ## Next.js default prefetching[​](#nextjs-default-prefetching "Direct link to Next.js default prefetching") Next.js's default prefetching method prefetches when links enter the viewport, this is a great user experience but can lead to unnecessary data transfer for bigger websites. For example by scrolling down the [Next.js homepage](https://nextjs.org/) it triggers **\~1.59MB** of prefetch requests as every single link on the page gets prefetched, regardless of user intent. To avoid this, we can wrap the `Link` component and add ForesightJS. The official Next.js [prefetching docs](https://nextjs.org/docs/app/guides/prefetching#extending-or-ejecting-link) mention ForesightJS as an example for custom prefetching strategies. ## ForesightLink Component[​](#foresightlink-component "Direct link to ForesightLink Component") Below is an example of creating an wrapper around the Next.js `Link` component that prefetches with ForesightJS. On mobile devices ForesightJS uses the configured [`touchDeviceStrategy`](/docs/configuration/global-settings.md#touch-device-strategy-v330). This implementation uses the `useForesight` react hook which can be found [here](/docs/integrations/react/useForesight.md). ``` "use client" import type { LinkProps } from "next/link" import Link from "next/link" import { type ForesightRegisterOptions } from "js.foresight" import useForesight from "../hooks/useForesight" import { useRouter } from "next/navigation" interface ForesightLinkProps extends Omit, Omit { children: React.ReactNode className?: string } export function ForesightLink({ children, className, ...props }: ForesightLinkProps) { const router = useRouter() // import from "next/navigation" not "next/router" const { elementRef } = useForesight({ callback: () => { router.prefetch(props.href.toString()) }, hitSlop: props.hitSlop, name: props.name, meta: props.meta, reactivateAfter: props.reactivateAfter, }) return ( {children} ) } ``` ## Basic Usage[​](#basic-usage "Direct link to Basic Usage") ``` import ForesightLink from "./ForesightLink" export default function Navigation() { return ( Home ) } ``` caution If you dont see the correct prefetching behaviour make sure you are in production. Next.js only prefetches in production and not in development --- # React Router ## React Router's Prefetching[​](#react-routers-prefetching "Direct link to React Router's Prefetching") React Router DOM (v6.4+) uses no prefetching by default. While you can enable prefetching with options like `intent` (hover/focus) or `viewport`, it doesnt have the same flexibility as ForesightJS. To add ForesightJS to React Router you can create a `ForesightLink` component wrapping the `Link` component. ## ForesightLink Component[​](#foresightlink-component "Direct link to ForesightLink Component") Below is an example of creating an wrapper around the React Router `Link` component that prefetches with ForesightJS. On mobile devices ForesightJS uses the configured [`touchDeviceStrategy`](/docs/configuration/global-settings.md#touch-device-strategy-v330). This implementation uses the `useForesight` react hook which can be found [here](/docs/integrations/react/useForesight.md). ``` "use client" import type { ForesightRegisterOptions } from "js.foresight" import { useState } from "react" import { Link, PrefetchPageLinks, type LinkProps } from "react-router" import useForesight from "./useForesight" interface ForesightLinkProps extends Omit, Omit { children: React.ReactNode className?: string } export function ForesightLink({ children, className, ...props }: ForesightLinkProps) { const [shouldPrefetch, setShouldPrefetch] = useState(false) const { elementRef, registerResults } = useForesight({ callback: () => { setShouldPrefetch(true) }, hitSlop: props.hitSlop, name: props.name, meta: props.meta reactivateAfter: props.reactivateAfter, }) return ( <> {shouldPrefetch && } {children} ) } ``` ### Usage of ForesightLink[​](#usage-of-foresightlink "Direct link to Usage of ForesightLink") ``` export function Navigation() { return ( <> contact about ) } ``` --- # useForesight The `useForesight` hook serves as the base for all ForesightJS usage with any React framework. ## useForesight[​](#useforesight-1 "Direct link to useForesight") ``` import { useRef, useEffect } from "react" import { ForesightManager, type ForesightRegisterOptionsWithoutElement, type ForesightRegisterResult, } from "js.foresight" export default function useForesight( options: ForesightRegisterOptionsWithoutElement ) { const elementRef = useRef(null) const registerResults = useRef(null) useEffect(() => { if (!elementRef.current) return registerResults.current = ForesightManager.instance.register({ element: elementRef.current, ...options, }) }, [options]) return { elementRef, registerResults } } ``` ### Return Values[​](#return-values "Direct link to Return Values") The hook returns an object containing: * `elementRef` - To attach to your target element * [`registerResults`](/docs/configuration/element-settings.md#return-properties) - Registration details like `isRegistered` **Important:** Due to React's rendering lifecycle, both `elementRef` and `registerResults` will be `null` during the initial render. The element gets registered only after the component mounts and the ref is attached. This means while implementing fallback prefetching logic, don't check if `registerResults` is `null`. Instead, always check the registration status using `registerResults.isRegistered` or device capabilities like `registerResults.isTouchDevice` and `registerResults.isLimitedConnection`. ### Basic Usage[​](#basic-usage "Direct link to Basic Usage") ``` import useForesight from "./useForesight" function MyComponent() { const { elementRef, registerResults } = useForesight({ callback: () => { console.log("Prefetching data...") // Your prefetch logic here }, hitSlop: 10, name: "my-button", }) return } ``` ### Framework Integrations[​](#framework-integrations "Direct link to Framework Integrations") For ready-to-use components built on top of useForesight, see our framework-specific integrations: * [React Router](/docs/integrations/react/react-router.md#foresightlink-component) * [Next.js](/docs/integrations/react/nextjs.md#foresightlink-component) --- # Vue Vue integration examples coming soon. --- # AI Context ForesightJS provides an `llms.txt` file following the [llms.txt specification](https://llmstxt.org/) to help LLMs understand and work with the library effectively. ## What is llms.txt?[​](#what-is-llmstxt "Direct link to What is llms.txt?") The `llms.txt` file is a standardized way to provide LLM-friendly information about a project. It contains: * A concise overview of what ForesightJS does * Links to key documentation sections in markdown format * Essential information for understanding the API and usage patterns ## Using ForesightJS's llms.txt[​](#using-foresightjss-llmstxt "Direct link to Using ForesightJS's llms.txt") ### Quick Access[​](#quick-access "Direct link to Quick Access") * **Main file:** [foresightjs.com/llms.txt](https://foresightjs.com/llms.txt) * **Full context:** [foresightjs.com/llms-full.txt](https://foresightjs.com/llms-full.txt) ### Example Prompt[​](#example-prompt "Direct link to Example Prompt") ``` Read the ForesightJS documentation from: https://foresightjs.com/llms.txt I need to implement ForesightJS predictive prefetching in my Next.js application. I want to: 1. Use the useForesight React hook for my custom link components 2. Integrate with Next.js router prefetching using ForesightLink 3. Use the default configuration, except that I want mobile prefetching to be on touch start ``` ## Individual Page Markdown[​](#individual-page-markdown "Direct link to Individual Page Markdown") All documentation pages are available as markdown by adding `.md` to any URL: * `https://foresightjs.com/docs/getting-started/your-first-element.md` * `https://foresightjs.com/docs/configuration/global-settings.md` * `https://foresightjs.com/docs/integrations/react/nextjs.md` --- # Behind the Scenes note Reading this is not necessary to use the library; this is just for understanding how ForesightJS works. ## Architecture[​](#architecture "Direct link to Architecture") ForesightJS uses a singleton pattern with `ForesightManager` as the central instance managing all prediction logic, registered elements, and global settings. Elements are stored in a `Map` where the key is the registered element itself. Multiple registrations of the same element overwrite the existing entry. Since DOM elements can change position and we need to keep the DOM clean, we require `element.getBoundingClientRect()` for each element on each update. To avoid triggering reflows, ForesightJS uses observers: * **`MutationObserver`**: Detects when registered elements are removed from the DOM for automatic unregistration * **`PositionObserver`**: Shopify's library that asynchronously monitors element position changes without polling The `PositionObserver` uses layered observation: 1. `VisibilityObserver` (built on `IntersectionObserver`) determines if elements are viewport-visible 2. `ResizeObserver` tracks size changes of visible target elements 3. Target-specific `IntersectionObserver` instances with smart rootMargin calculations transform viewport observation into target-specific observation regions ## Keyboard & Mouse Users[​](#keyboard--mouse-users "Direct link to Keyboard & Mouse Users") ### Mouse Prediction[​](#mouse-prediction "Direct link to Mouse Prediction") Mouse prediction tracks cursor movement patterns to anticipate click targets by analyzing velocity and trajectory. **Event Handling**: `mousemove` events record `clientX` and `clientY` coordinates. Position history is limited by `positionHistorySize` setting. **Prediction Algorithm**: The `predictNextMousePosition` function implements linear extrapolation: 1. **History Tracking**: Stores past mouse positions with timestamps 2. **Velocity Calculation**: Calculates average velocity using oldest and newest points in history 3. **Extrapolation**: Projects current position along trajectory using calculated velocity and `trajectoryPredictionTimeInMs` setting **Intersection Detection**: The `lineSegmentIntersectsRect` function implements the Liang-Barsky line clipping algorithm to check intersections between the predicted mouse path and element rectangles: 1. Line segment defined by current and predicted mouse positions 2. Target rectangle includes element's `hitSlop` 3. Algorithm clips line segment against rectangle's four edges 4. Intersection confirmed if entry parameter ≤ exit parameter ### Keyboard Prediction[​](#keyboard-prediction "Direct link to Keyboard Prediction") Tab prediction monitors keyboard navigation by detecting Tab key presses and focus changes. **Event Handling**: * `keydown`: Detects Tab key presses * `focusin`: Fires when elements gain focus When `focusin` follows a Tab `keydown`, ForesightJS identifies this as tab navigation. **Tab Navigation Logic**: Uses the `tabbable` library to determine tab order. Results are cached for performance since `tabbable()` calls `getBoundingClientRect()` internally. Cache invalidates on DOM mutations. Prediction process: 1. Identify current focused element's index in tabbable elements list 2. Determine tab direction (forward/backward based on Shift key) 3. Calculate prediction range using current index, direction, and `tabOffset` 4. Trigger callbacks for registered elements within predicted range ### Scroll Prediction[​](#scroll-prediction "Direct link to Scroll Prediction") Leverages existing observer infrastructure without additional event listeners. The `PositionObserver` callback provides elements that have moved. By analyzing the delta between previous and new `boundingClientRect` for viewport-remaining elements, ForesightJS infers scroll direction. Note: This also triggers during animations or dynamic layout changes. ## Touch Device Users[​](#touch-device-users "Direct link to Touch Device Users") Touch devices require different prediction strategies due to the lack of continuous cursor movement. ForesightJS provides two strategies via the `touchDeviceStrategy` prop: **Viewport Entry**: Uses `IntersectionObserver` to detect when elements enter the viewport. Callbacks trigger when elements become visible during scrolling, providing anticipatory loading for content that will soon be in view. **Touch Start**: Listens for `touchstart` events on registered elements. Callbacks fire immediately when users begin touching an element, before the actual `click` event occurs, providing a performance advantage for touch interactions. ## Element Lifecycle[​](#element-lifecycle "Direct link to Element Lifecycle") 1. **Registration**: `ForesightManager.instance.register(element, options)` adds element to internal Map 2. **Callback Execution**: When user intent is detected, element's callback function triggers 3. **Deactivation**: `isCallbackActive` set to `false` after callback execution 4. **Reactivation Wait**: Element remains deactivated for `reactivateAfter` duration (defaults to infinity) 5. **Reactivation**: `isCallbackActive` reset to `true` after duration elapses **Cleanup**: Elements removed via `ForesightManager.instance.unregister(element)` or automatically when DOM removal is detected by `MutationObserver`. --- # Element Settings When registering elements with the `ForesightManager`, you can provide configuration specific to each element. This allows you to fine-tune prediction behavior on a per-element basis. ## Basic Element Registration[​](#basic-element-registration "Direct link to Basic Element Registration") ``` const myElement = document.getElementById("my-element") const { isTouchDevice, isLimitedConnection, isRegistered } = ForesightManager.instance.register({ element: myElement, // Required: The element to monitor callback: () => { // Required: Function that executes when interaction is predicted console.log("prefetching") }, hitSlop: 50, // slop around the element, making its hitbox bigger name: "My Foresight button!", // name visible in the debug tools meta: { route: "/about", }, // your custom meta data for analytics reactivateAfter: 5 * 60 * 1000, // time for the element to reactivate after the callback has been hit }) ``` ## Registration Parameters[​](#registration-parameters "Direct link to Registration Parameters") **TypeScript Type:** `ForesightRegisterOptions` or `ForesightRegisterOptionsWithoutElement` if you want to omit the `element` ### Required Parameters[​](#required-parameters "Direct link to Required Parameters") #### `element`[​](#element "Direct link to element") * **Type:** `element` * **Required:** Yes * **Description:** The DOM element to monitor for user interactions. ``` const button = document.querySelector("#my-button") ForesightManager.instance.register({ element: button, // Any DOM element callback: () => { /* prefetch logic */ }, }) ``` *** #### `callback`[​](#callback "Direct link to callback") * **Type:** `function` * **Required:** Yes * **Description:** Function that executes when interaction is predicted or occurs. This is where your prefetching logic goes. * **Note:** If you await your prefetch logic the `callbackCompleted` [event](/docs/events.md#callbackcompleted) will show you how long your prefetch took to run. ``` ForesightManager.instance.register({ element: myElement, callback: () => { // prefetch logic }, }) ``` *** ### Optional Parameters[​](#optional-parameters "Direct link to Optional Parameters") #### `hitSlop`[​](#hitslop "Direct link to hitslop") * **Type:** `number | Rect` * **Required:** No * **Default:** Uses `defaultHitSlop` from global settings (which is 0 if not set) * **Description:** Fully invisible "slop" around the element that increases the hover hitbox. ``` // Uniform hit slop (20px on all sides) ForesightManager.instance.register({ element: myElement, callback: () => {}, hitSlop: 20, }) // Custom hit slop for each side ForesightManager.instance.register({ element: myElement, callback: () => {}, hitSlop: { top: 10, left: 50, right: 50, bottom: 100, }, }) ``` *** #### `name`[​](#name "Direct link to name") * **Type:** `string` * **Required:** No * **Default:** `element.id` or `"unnamed"` if no id * **Description:** A descriptive name for the element, useful for development tools and debugging. ``` ForesightManager.instance.register({ element: myElement, callback: () => { /* logic */ }, name: "Product Navigation Button", // Helpful for debugging }) ``` *** #### `reactivateAfter`[​](#reactivateafter "Direct link to reactivateafter") * **Type:** `number` * **Required:** No * **Default:** `Infinity` * **Description:** Time in milliseconds after which the callback can be fired again. Set to `Infinity` to prevent callback from firing again after first execution. * **Note:** Even though `ForesightJS` doesn't store any data, you can set this to the same time as your data would become `stale` normally. In most cases you can just leave this on the default value, as it will get reset on page refresh/rerouting anyways. ``` ForesightManager.instance.register({ element: myElement, callback: () => {}, reactivateAfter: 5 * 60 * 1000, // 5 minutes }) ``` *** #### `meta`[​](#meta "Direct link to meta") * **Type:** `Record` * **Required:** No * **Default:** `{}` * **Description:** Stores additional information about the registered element. Visible in all element-related [events](/docs/events.md) and in the [devtools](/docs/debugging/devtools.md). ``` ForesightManager.instance.register({ element: productLink, callback: () => {}, meta: { route: "/about", section: "main", category: "content", }, }) ``` ## Registration Return Value[​](#registration-return-value "Direct link to Registration Return Value") The `register()` method returns useful information about the registration: **TypeScript Type:** `ForesightRegisterResult` ``` const { isTouchDevice, isLimitedConnection, isRegistered } = ForesightManager.instance.register({ element: myElement, callback: () => {}, }) ``` *** ### Return Properties[​](#return-properties "Direct link to Return Properties") #### `isTouchDevice`[​](#istouchdevice "Direct link to istouchdevice") * **Type:** `boolean` * **Description:** Indicates whether the current device is a touch device. *** #### `isLimitedConnection`[​](#islimitedconnection "Direct link to islimitedconnection") * **Type:** `boolean` * **Description:** Is `true` when the user's connection matches the configured `minimumConnectionType` setting (defaults to "3g"). Elements will not be registered when connection is limited. See [Global Settings](/docs/configuration/global-settings.md#minimumconnectiontype) for details. * **Note:** This feature relies on the [Network Information API](https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation) which is not supported in all major browsers. When the API is not available, ForesightJS will register elements regardless of connection speed. *** #### `isRegistered`[​](#isregistered "Direct link to isregistered") * **Type:** `boolean` * **Description:** If `isLimitedConnection` is `true`, this will be `false`. Otherwise indicates successful registration. --- # Global Settings Global settings are specified when initializing the `ForesightManager` and apply to all registered elements. This should be done once at your application's entry point. tip If you want the default global options you don't need to initialize the ForesightManager explicitly. ## Basic Configuration[​](#basic-configuration "Direct link to Basic Configuration") ``` import { ForesightManager } from "js.foresight" // Initialize the manager once at the top of your app if you want custom global settings // ALL SETTINGS ARE OPTIONAL ForesightManager.initialize({ enableMousePrediction: true, positionHistorySize: 8, trajectoryPredictionTime: 80, defaultHitSlop: 10, enableTabPrediction: true, tabOffset: 3, enableScrollPrediction: true, scrollMargin: 150, touchDeviceStrategy: "viewport", enableManagerLogging: false, minimumConnectionType: "3g", }) ``` ## Available Settings[​](#available-settings "Direct link to Available Settings") **TypeScript Type:** `ForesightManagerSettings` note All numeric settings are clamped to their specified Min/Max values to prevent invalid configurations. ### Mouse Prediction Settings[​](#mouse-prediction-settings "Direct link to Mouse Prediction Settings") #### `enableMousePrediction`[​](#enablemouseprediction "Direct link to enablemouseprediction") * **Type:** `boolean` * **Default:** `true` * **Description:** Toggles whether trajectory prediction is active. If `false`, only direct hovers will trigger the callback for mouse users. * **Note:** When this is turned off `ForesightJS` will still trigger `callbacks` while hovering over the element's extended range (`rect` + `hitslop`) ``` ForesightManager.initialize({ enableMousePrediction: false, // Disable trajectory prediction }) ``` *** #### `positionHistorySize`[​](#positionhistorysize "Direct link to positionhistorysize") * **Type:** `number` * **Default:** `8` * **Clamped between:** `2-30` * **Description:** Number of mouse positions to keep in history for velocity calculations. Higher values provide smoother predictions but use more memory. ``` ForesightManager.initialize({ positionHistorySize: 12, // Keep more position history for smoother predictions }) ``` *** #### `trajectoryPredictionTime`[​](#trajectorypredictiontime "Direct link to trajectorypredictiontime") * **Type:** `number` * **Default:** `120` * **Clamped between:** `10-200` milliseconds * **Description:** How far ahead (in milliseconds) to predict the mouse trajectory. Larger values trigger callbacks sooner but may reduce accuracy. ``` ForesightManager.initialize({ trajectoryPredictionTime: 100, // Predict 100ms into the future }) ``` *** ### Keyboard Prediction Settings[​](#keyboard-prediction-settings "Direct link to Keyboard Prediction Settings") #### `enableTabPrediction`[​](#enabletabprediction "Direct link to enabletabprediction") * **Type:** `boolean` * **Default:** `true` * **Description:** Toggles whether keyboard prediction is active for tab navigation. ``` ForesightManager.initialize({ enableTabPrediction: false, // Disable keyboard prediction }) ``` *** #### `tabOffset`[​](#taboffset "Direct link to taboffset") * **Type:** `number` * **Default:** `2` * **Clamped between:** `0-20` * **Description:** Number of tab stops away from an element to trigger its callback. Only works when `enableTabPrediction` is `true`. ``` ForesightManager.initialize({ tabOffset: 1, // Trigger callback when 1 tab stop away }) ``` *** ### Scroll Prediction Settings[​](#scroll-prediction-settings "Direct link to Scroll Prediction Settings") #### `enableScrollPrediction`[​](#enablescrollprediction "Direct link to enablescrollprediction") * **Type:** `boolean` * **Default:** `true` * **Description:** Toggles whether scroll prediction is active. ``` ForesightManager.initialize({ enableScrollPrediction: false, // Disable scroll prediction }) ``` *** #### `scrollMargin`[​](#scrollmargin "Direct link to scrollmargin") * **Type:** `number` * **Default:** `150` * **Clamped between:** `30-300` pixels * **Description:** Sets the pixel distance to check from the mouse position in the scroll direction for triggering callbacks. ``` ForesightManager.initialize({ scrollMargin: 200, // Check 200px ahead in scroll direction }) ``` *** ### Hit Slop Settings[​](#hit-slop-settings "Direct link to Hit Slop Settings") #### `defaultHitSlop`[​](#defaulthitslop "Direct link to defaulthitslop") * **Type:** `number | Rect` * **Default:** `{top: 0, left: 0, right: 0, bottom: 0}` * **Clamped between:** `0-2000` pixels (per side) * **Description:** Default fully invisible "slop" around elements for all registered elements. Basically increases the hover hitbox. * **Note:** This can be overwritten on an element basis by setting its `hitslop`. ``` // Uniform hit slop ForesightManager.initialize({ defaultHitSlop: 20, // 20px on all sides }) // Custom hit slop for each side ForesightManager.initialize({ defaultHitSlop: { top: 10, left: 20, right: 20, bottom: 15, }, }) ``` *** ### Touch Device Settings (v3.3.0+)[​](#touch-device-settings-v330 "Direct link to Touch Device Settings (v3.3.0+)") #### `touchDeviceStrategy`[​](#touchdevicestrategy "Direct link to touchdevicestrategy") * **Type:** `TouchDeviceStrategy` * **Default:** `"onTouchStart"` * **Options:** `"none"`, `"viewport"`, `"onTouchStart"` * **Description:** Strategy to use for touch devices (mobile / pen users). ``` ForesightManager.initialize({ touchDeviceStrategy: "viewport", }) ``` *** ### Minimum Connection Settings[​](#minimum-connection-settings "Direct link to Minimum Connection Settings") #### `minimumConnectionType`[​](#minimumconnectiontype "Direct link to minimumconnectiontype") * **Type:** `MinimumConnectionType` * **Default:** `"3g"` * **Options:** `"slow-2g"`, `"2g"`, `"3g"`, `"4g"` * **Description:** The minimum connection speed required to register elements. Elements will not be registered when the user's connection is slower than this threshold. * **Note:** This feature relies on the [Network Information API](https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation) which is not supported in all major browsers. When the API is not available, ForesightJS will register elements regardless of connection speed. ``` ForesightManager.initialize({ minimumConnectionType: "3g", }) ``` *** ### Other Settings[​](#other-settings "Direct link to Other Settings") #### `enableManagerLogging`[​](#enablemanagerlogging "Direct link to enablemanagerlogging") * **Type:** `boolean` * **Default:** `"false"` * **Description:** Logs basic information about the `ForesightManager` and its handlers that is not available through [events](/docs/events.md). Mostly used by the maintainers of `ForesightJS` to debug the manager, but might be useful for implementers aswell. * **Note:** Examples of logs are: Initializing the manager, switching from device strategy (e.g. Mouse to pen), aborting controllers and invalidating cache. ``` ForesightManager.initialize({ enableManagerLogging: true, }) ``` ## Runtime Configuration Changes[​](#runtime-configuration-changes "Direct link to Runtime Configuration Changes") You can update global settings at runtime using `alterGlobalSettings()`. This is not adviced for regular use and is mainly used for developers creating tools on top of `Foresight`. For regular use, you should set global settings during initialization with `ForesightManager.initialize()`. ``` // Change settings after initialization ForesightManager.instance.alterGlobalSettings({ trajectoryPredictionTime: 100, enableScrollPrediction: false, }) ``` This is particularly useful when integrating with development tools or when you need to adjust settings based on user preferences or device capabilities. ## Development Tools Integration[​](#development-tools-integration "Direct link to Development Tools Integration") Development Tools Visual development tools are available as a separate package. See the [development tools documentation](/docs/debugging/devtools.md) for details on installing and configuring the `js.foresight-devtools` package. The development tools provide a real-time interface for adjusting these global settings and seeing their immediate effects on prediction behavior. --- # Development Tools [![npm version](https://img.shields.io/npm/v/js.foresight-devtools.svg)](https://www.npmjs.com/package/js.foresight-devtools) [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) `ForesightJS` offers dedicated [Development Tools](https://github.com/spaansba/ForesightJS/tree/main/packages/js.foresight-devtools), written in [Lit](https://lit.dev/), to help you better understand and fine-tune how `ForesightJS` works within your application. You can see the development tools in action on the [playground page](https://foresightjs.com/#playground), which includes visual trajectory indicators, element boundaries, and a control panel in the bottom-right corner. These tools are built entirely using `ForesightJS`'s [built-in events](/docs/events.md), demonstrating how you can create your own monitoring and debugging tools using the same event system. ## Installation[​](#installation "Direct link to Installation") To install the `ForesightJS` Development Tools package, use your preferred package manager: ``` pnpm add -D js.foresight-devtools # or npm install -D js.foresight-devtools # or yarn add -D js.foresight-devtools ``` ## Enabling Development Tools[​](#enabling-development-tools "Direct link to Enabling Development Tools") ``` import { ForesightManager } from "js.foresight" import { ForesightDevtools } from "js.foresight-devtools" // Initialize ForesightJS ForesightManager.initialize({}) // Initialize the development tools (all options are optional) ForesightDevtools.initialize({ showDebugger: true, isControlPanelDefaultMinimized: false, // optional setting which allows you to minimize the control panel on default showNameTags: true, // optional setting which shows the name of the element sortElementList: "visibility", // optional setting for how the elements in the control panel are sorted logging: { logLocation: "controlPanel", // Where to log the Foresight Events callbackCompleted: true, elementReactivated: true, callbackInvoked: true, elementDataUpdated: false, elementRegistered: false, elementUnregistered: false, managerSettingsChanged: true, mouseTrajectoryUpdate: false, // dont log this to the devtools scrollTrajectoryUpdate: false, // dont log this to the devtools deviceStrategyChanged: true, }, }) ``` ## Development Tools Features[​](#development-tools-features "Direct link to Development Tools Features") Once enabled, the `ForesightJS` Development Tools add several visual layers to your application, including mouse and scroll trajectories and element hitboxes. A control panel also appears in the bottom-right corner of the screen. ### Control Panel[​](#control-panel "Direct link to Control Panel") The control panel provides three main tabs for debugging and configuration. Each tab serves a specific purpose in understanding and tuning ForesightJS behavior. #### Settings Tab[​](#settings-tab "Direct link to Settings Tab") The Settings tab provides real-time controls for all [Global Configurations](/docs/configuration/global-settings.md). Changes made through these controls immediately affect the `ForesightManager` configuration, allowing you to see how different settings impact your app without fiddling in your code. #### Elements Tab[​](#elements-tab "Direct link to Elements Tab") The Elements tab displays a overview of all currently registered elements within the `ForesightManage`r. Each element entry shows its current status through color-coded indicators: * 🟢 **Green** - Active visible elements in desktop mode * ⚫ **Grey** - Active invisible elements in desktop mode * 🟣 **Purple** - Active elements while in touch device mode (all elements, we dont track visibility in this mode) * 🟡 **Yellow** - Elements which callbacks are currently executing * 🔘 **Light Gray** - Inactive elements Each element can also be expanded to reveal its [`ForesightElementData`](/docs/next/getting-started/typescript.md#foresightelementdata) information including settings, callback status, and metadata. A countdown timer appears for elements in their reactivation cooldown period (`reactivateAfter`), clicking this timer will instantly reactivate the element. #### Log Tab[​](#log-tab "Direct link to Log Tab") The Log tab displays real-time [events](/docs/events.md) emitted by `ForesightJS`. You can see callback execution times, the full element's lifecycle and other system events. Events can be filtered through the devtools initialization configuration or in the control panel itself. You can also print out the complete [`ForesightManager.instance.getManagerData`](/docs/debugging/static-properties.md#foresightmanagerinstancegetmanagerdata) state without having to call it from your code. caution Avoid logging frequently emitted events to the browser console, as it can noticeably slow down your development environment. Use the control panel for this instead. note Element overlay visualization and visibility sorting in the control panel only work with desktop/mouse prediction strategies. When debugging `touchDeviceStrategy` configurations, these features are not available as touch strategies don't track the same positioning data. --- # Static Properties The ForesightManager exposes several static properties for accessing and checking the manager state. ***All properties are read-only*** ## ForesightManager.instance[​](#foresightmanagerinstance "Direct link to ForesightManager.instance") Gets the singleton instance of ForesightManager, initializing it if necessary. This is the primary way to access the manager throughout your application. **Returns:** `ForesightManager` **Example:** ``` const manager = ForesightManager.instance // Register an element manager.register({ element: myButton, callback: () => console.log("Predicted interaction!"), }) // or ForesightManager.instance.register({ element: myButton, callback: () => console.log("Predicted interaction!"), }) ``` ## ForesightManager.instance.registeredElements[​](#foresightmanagerinstanceregisteredelements "Direct link to ForesightManager.instance.registeredElements") Gets a Map of all currently registered elements and their associated data. This is useful for debugging or inspecting the current state of registered elements. **Returns:** `ReadonlyMap` ## ForesightManager.instance.isInitiated[​](#foresightmanagerinstanceisinitiated "Direct link to ForesightManager.instance.isInitiated") Checks whether the ForesightManager has been initialized. **Returns:** `Readonly` ## ForesightManager.instance.getManagerData[​](#foresightmanagerinstancegetmanagerdata "Direct link to ForesightManager.instance.getManagerData") Snapshot of the current `ForesightManager` state, including all [global settings](/docs/configuration/global-settings.md), registered elements, position observer data, and interaction statistics. This is primarily used for debugging, monitoring, and development purposes. **Properties:** * `registeredElements` - `Map` of all currently registered elements and their associated data * `eventListeners` - `Map` of all event listeners listening to [ForesightManager Events](/docs/events.md). * `globalSettings` - Current [global configuration](/docs/configuration/global-settings.md) settings * `globalCallbackHits` - Total `callback` execution counts by interaction type (mouse/tab/scroll/viewport/touch) and by subtype (hover/trajctory for mouse, forwards/reverse for tab, direction for scroll) * `currentDeviceStrategy` - Which strategy is being used. Can be either `touch` or `mouse`, this changes dynamically * `activeElementCount` - Amount of elements currently active (not the same as registered) **Returns:** `Readonly` The return will look something like this: ``` { "registeredElements": { "size": 7, "entries": "" }, "activeElementCount": 5, "currentDeviceStrategy": "mouse", "eventListeners": { "0": { "elementRegistered": [] }, "1": { "elementUnregistered": [] }, "2": { "elementDataUpdated": [] }, "3": { "mouseTrajectoryUpdate": [] }, "4": { "scrollTrajectoryUpdate": [] }, "5": { "managerSettingsChanged": [] }, "6": { "callbackFired": [] } }, "globalSettings": { "defaultHitSlop": { "bottom": 10, "left": 10, "right": 10, "top": 10 }, "enableMousePrediction": true, "enableScrollPrediction": true, "enableTabPrediction": true, "positionHistorySize": 10, "resizeScrollThrottleDelay": 0, "scrollMargin": 150, "tabOffset": 2, "trajectoryPredictionTime": 100 }, "globalCallbackHits": { "mouse": { "hover": 0, "trajectory": 3 }, "scroll": { "down": 2, "left": 0, "right": 0, "up": 0 }, "tab": { "forwards": 3, "reverse": 0 }, "touch": 0, "viewport": 0, "total": 8 } } ``` --- # Events ForesightManager emits various events during to provide insight into element registration, prediction activities, and callback executions. These events are primarily used by the [ForesightJS DevTools](/docs/debugging/devtools.md) for visual debugging and monitoring, but can also be leveraged for telemetry, analytics, and performance monitoring in your applications. ## Usage[​](#usage "Direct link to Usage") All events are visible in the logs tab of the [devtools](/docs/debugging/devtools.md). However for tracking/analytics in production, implementing them in your own code is straightforward with the standard `addEventListener` pattern. ``` import { ForesightManager } from "js.foresight" // Define handler as const for removal const handleCallbackInvoked = event => { console.log( `Callback executed for ${event.elementData.name} in ${event.hitType.kind} mode, which took ${event.elapsed} ms` ) } // Add the event ForesightManager.instance.addEventListener("callbackInvoked", handleCallbackInvoked) // Later, remove the listener using the same reference ForesightManager.instance.removeEventListener("callbackInvoked", handleCallbackInvoked) ``` ### AbortController support[​](#abortcontroller-support "Direct link to AbortController support") Event listeners support [AbortController signals](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) for easy cleanup. ``` const controller = new AbortController() manager.addEventListener("callbackInvoked", handleCallbackInvoked, { signal: controller.signal }) controller.abort() ``` ## Available Events[​](#available-events "Direct link to Available Events") ### Interaction Events[​](#interaction-events "Direct link to Interaction Events") Events fired when user interactions trigger callbacks. *** #### `callbackInvoked`[​](#callbackinvoked "Direct link to callbackinvoked") Fired **before** an element's callback is executed ``` type CallbackInvokedEvent = { type: "callbackInvoked" timestamp: number elementData: ForesightElementData hitType: HitType } ``` **Related Types:** [`CallbackHitType`](/docs/getting-started/typescript.md#callbackhittype) • [`ForesightElementData`](/docs/getting-started/typescript.md#foresightelementdata) *** #### `callbackCompleted`[​](#callbackcompleted "Direct link to callbackcompleted") Fired **after** an element's callback is executed ``` type CallbackCompletedEvent = { type: "callbackCompleted" timestamp: number elementData: ForesightElementData hitType: CallbackHitType elapsed: number // Time between callbackInvoked and callbackCompleted status: "success" | "error" | undefined errorMessage: string | undefined | null wasLastActiveElement: boolean } ``` **Related Types:** [`CallbackHitType`](/docs/getting-started/typescript.md#callbackhittype) • [`ForesightElementData`](/docs/getting-started/typescript.md#foresightelementdata) ### Element Lifecycle Events[​](#element-lifecycle-events "Direct link to Element Lifecycle Events") Events fired during element registration, updates, and cleanup. *** #### `elementRegistered`[​](#elementregistered "Direct link to elementregistered") Fired when an element is successfully registered with `ForesightManager` using `foresightmanager.instance.register(element)`. ``` type ElementRegisteredEvent = { type: "elementRegistered" timestamp: number elementData: ForesightElementData } ``` **Related Types:** [`ForesightElementData`](/docs/getting-started/typescript.md#foresightelementdata) *** #### `elementReactivated`[​](#elementreactivated "Direct link to elementreactivated") Fired when an element is reactivated after its callback was triggered. This happends after `reactivateAfter` ms (default infinity) or with `foresightmanager.instance.reactivate(element)`. ``` type ElementReactivatedEvent = { type: "elementReactivated" timestamp: number elementData: ForesightElementData } ``` **Related Types:** [`ForesightElementData`](/docs/getting-started/typescript.md#foresightelementdata) *** #### `elementUnregistered`[​](#elementunregistered "Direct link to elementunregistered") Fired when an element is removed from `ForesightManager`'s tracking. This only happends when the element is removed from the `DOM` or via developer actions like `foresightmanar.instance.unregister(element)` ``` type ElementUnregisteredEvent = { type: "elementUnregistered" timestamp: number elementData: ForesightElementData unregisterReason: "disconnected" | "apiCall" | "devtools" | (string & {}) wasLastRegisteredElement: boolean } ``` **Related Types:** [`ForesightElementData`](/docs/getting-started/typescript.md#foresightelementdata) *** #### `elementDataUpdated`[​](#elementdataupdated "Direct link to elementdataupdated") Fired when tracked element data changes (bounds/visibility only). Does not fire on any updates regarding `callback` data. ``` type ElementDataUpdatedEvent = { type: "elementDataUpdated" elementData: ForesightElementData updatedProps: UpdatedDataPropertyNames[] // "bounds" | "visibility" } ``` **Related Types:** [`ForesightElementData`](/docs/getting-started/typescript.md#foresightelementdata) ### Prediction Events[​](#prediction-events "Direct link to Prediction Events") Events fired during movement prediction calculations. *** #### `mouseTrajectoryUpdate`[​](#mousetrajectoryupdate "Direct link to mousetrajectoryupdate") Fired during mouse movement for trajectory calculations ``` type MouseTrajectoryUpdateEvent = { type: "mouseTrajectoryUpdate" trajectoryPositions: { currentPoint: { x: number; y: number } predictedPoint: { x: number; y: number } } predictionEnabled: boolean } ``` *** #### `scrollTrajectoryUpdate`[​](#scrolltrajectoryupdate "Direct link to scrolltrajectoryupdate") Fired during scroll events when scroll prediction is active ``` type ScrollTrajectoryUpdateEvent = { type: "scrollTrajectoryUpdate" currentPoint: Point // { x: number; y: number } predictedPoint: Point // { x: number; y: number } scrollDirection: ScrollDirection // "down" | "up" | "left" | "right" } ``` *** ### Configuration Events[​](#configuration-events "Direct link to Configuration Events") Events fired when ForesightManager configuration changes. *** #### `managerSettingsChanged`[​](#managersettingschanged "Direct link to managersettingschanged") Fired when [global](/docs/configuration/global-settings.md) `ForesightManager` settings are updated via the [devtools](/docs/debugging/devtools.md) or via `foresightmanager.instance.alterGlobalSettings()`. ``` type ManagerSettingsChangedEvent = { type: "managerSettingsChanged" timestamp: number managerData: Readonly updatedSettings: UpdatedManagerSetting[] } ``` #### Extra Type Info[​](#extra-type-info "Direct link to Extra Type Info") [`ForesightElementData`](/docs/getting-started/typescript.md#foresightelementdata) *** #### `deviceStrategyChanged`[​](#devicestrategychanged "Direct link to devicestrategychanged") Fired when user switches from mouse+keyboard setup to pen/touch or vice versa. ``` type ManagerSettingsChangedEvent = { type: "deviceStrategyChanged" timestamp: number newStrategy: CurrentDeviceStrategy oldStrategy: CurrentDeviceStrategy } ``` *** --- # Initialize the Manager Optional Step This step is **not needed** if you don't want to configure ForesightJS. The manager will initialize automatically with default settings when you register your first element. If you don't want to configure anything, skip to [Your First Element](/docs/getting-started/your-first-element.md). If you want to customize ForesightJS's global behavior, you can initialize the manager with your preferred settings at your application's entry point. ## Optional Settings[​](#optional-settings "Direct link to Optional Settings") You can configure any of these [global settings](/docs/configuration/global-settings.md). ``` ForesightManager.initialize({ // Mouse prediction settings enableMousePrediction: true, // Enable/disable mouse trajectory prediction trajectoryPredictionTime: 120, // How far ahead to predict (10-200ms) positionHistorySize: 8, // Mouse positions to track (2-30) // Keyboard prediction settings enableTabPrediction: true, // Enable/disable keyboard navigation prediction tabOffset: 2, // Tab stops ahead to trigger (0-20) // Scroll prediction settings enableScrollPrediction: true, // Enable/disable scroll prediction scrollMargin: 150, // Pixel distance for scroll detection (30-300) // Touch device settings touchDeviceStrategy: "viewport", // "none", "viewport", or "onTouchStart" // Connection settings minimumConnectionType: "3g", // "slow-2g", "2g", "3g", "4g" // Default element settings defaultHitSlop: 0, // Default hit slop for all elements (number or {top, right, bottom, left}) }) ``` ## Good to know[​](#good-to-know "Direct link to Good to know") The `ForesightManager` is a singleton, meaning it can only be initialized once. If you call `ForesightManager.initialize()` multiple times, only the first call will take effect. Any subsequent calls will simply return the already initialized instance. The manager also provides several static methods you can use to get information about which elements are registered, how many callbacks have been ran and all the listeneing [event](/docs/events.md) listeners. You can find a list of these methods [here](/docs/debugging/static-properties.md). --- # TypeScript ForesightJS is fully written in `TypeScript` to make sure your development experience is as good as possbile. ## Most Used Internal types[​](#most-used-internal-types "Direct link to Most Used Internal types") ### ForesightElementData[​](#foresightelementdata "Direct link to ForesightElementData") This is the main type used in ForesightJS as it gathers all information about a registered element. `ForesightElementData` is returned in most [events](/docs/events.md) as `elementData` ``` type ForesightElementData = { callback: ForesightCallback // () => void name: string id: string // only for internals elementBounds: { originalRect: DOMRectReadOnly hitSlop: Exclude expandedRect: Readonly // originalRect + hitSlop } trajectoryHitData: TrajectoryHitData // only for internals isIntersectingWithViewport: boolean element: ForesightElement registerCount: number //Amount of times this element has been (re)registered. meta: Record callbackInfo: { callbackFiredCount: number lastCallbackInvokedAt: number | undefined lastCallbackCompletedAt: number | undefined lastCallbackRuntime: number | undefined lastCallbackStatus: callbackStatus lastCallbackErrorMessage: string | undefined | null reactivateAfter: number isCallbackActive: boolean isRunningCallback: boolean reactivateTimeoutId?: ReturnType } } ``` ### CallbackHitType[​](#callbackhittype "Direct link to CallbackHitType") ``` type CallbackHitType = | { kind: "mouse"; subType: "hover" | "trajectory" } | { kind: "tab"; subType: "forwards" | "reverse" } | { kind: "scroll"; subType: "up" | "down" | "left" | "right" } ``` ### ForesightManagerData[​](#foresightmanagerdata "Direct link to ForesightManagerData") Snapshot of the current ForesightManager state, including all [global settings](/docs/configuration/global-settings.md), registered elements, position observer data, and interaction statistics. This is primarily used for debugging, monitoring, and development purposes. This data is returned in the [`managerSettingsChanged`](/docs/events.md) event or by calling [`ForesightManager.instance.getManagerData`](/docs/debugging/static-properties.md#foresightmanagerinstancegetmanagerdata) manually. ``` type ForesightManagerData = { registeredElements: ReadonlyMap // See above for ForesightElementData globalSettings: Readonly // See configuration for global settings globalCallbackHits: Readonly // See above for CallbackHitType, this is that but all of them combined eventListeners: ReadonlyMap // All event listeners currently listening } ``` ## Helper Types[​](#helper-types "Direct link to Helper Types") ### ForesightRegisterOptionsWithoutElement[​](#foresightregisteroptionswithoutelement "Direct link to ForesightRegisterOptionsWithoutElement") Usefull for if you want to create a custom button component in a modern framework (for example React). And you want to have the `ForesightRegisterOptions` used in `ForesightManager.instance.register({})` without the element as the element will be the ref of the component. This type is used in the [`useForesight`](/docs/integrations/react/useForesight.md) hook for React. ``` type ForesightButtonProps = { registerOptions: ForesightRegisterOptionsWithoutElement } ``` --- # What is ForesightJS [![npm version](https://img.shields.io/npm/v/js.foresight.svg)](https://www.npmjs.com/package/js.foresight) [![npm downloads](https://img.shields.io/npm/dt/js.foresight.svg)](https://www.npmjs.com/package/js.foresight) [![Bundle Size](https://img.shields.io/bundlephobia/minzip/js.foresight)](https://bundlephobia.com/package/js.foresight) [![GitHub last commit](https://img.shields.io/github/last-commit/spaansba/ForesightJS)](https://github.com/spaansba/ForesightJS/commits) [![GitHub stars](https://img.shields.io/github/stars/spaansba/ForesightJS.svg?style=social\&label=Star)](https://github.com/spaansba/ForesightJS) [![Best of JS](https://img.shields.io/endpoint?url=https://bestofjs-serverless.now.sh/api/project-badge?fullName=spaansba%2FForesightJS%26since=daily)](https://bestofjs.org/projects/foresightjs) [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Demo](https://img.shields.io/badge/demo-live-blue)](https://foresightjs.com#playground) ForesightJS is a lightweight JavaScript library that predicts user intent to prefetch content before it's needed. **It works completely out of the box without configuration**, supporting both desktop and mobile devices with different prediction strategies. ## Understanding ForesightJS's Role[​](#understanding-foresightjss-role "Direct link to Understanding ForesightJS's Role") When you over simplify prefetching it exists of three parts: * **What** resource or data to load * **How** the loading method and caching strategy is * **When** the optimal moment to start fetching is ForesightJS takes care of the **When** by predicting user intent with mouse trajectory and tab navigation. You supply the **What** and **How** inside your `callback` when you register an element. ## Download[​](#download "Direct link to Download") ``` pnpm add js.foresight # or npm install js.foresight # or yarn add js.foresight ``` ## Prediction Strategies[​](#prediction-strategies "Direct link to Prediction Strategies") ForesightJS uses different prediction strategies depending on the device type. For limited connections (slower than the minimum connection type - default: 3G - or [data-saver mode](https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/saveData)), we respect the user's preference to minimize data usage and skip registration. Foresight automatically detects mouse, pen, or touch input, and updates its strategy if the input method changes. ### Keyboard/Mouse Users[​](#keyboardmouse-users "Direct link to Keyboard/Mouse Users") Pick and choose multiple prediction strategies. All are enabled by default, but you can disable any of them when [initializing](/docs/getting-started/initialize-the-manager.md) the `ForesightManager`. * **Mouse Trajectory** default - Analyzes cursor movement patterns to predict which links users are heading towards and prefetches content before they arrive * **Keyboard Navigation** default - Tracks tab key usage to prefetch when the user is N tab stops away from your registered element * **Scroll** default - Prefetches content when users scroll towards registered elements, predicting which elements will be reached based on scroll direction ### Touch Devices (v3.3.0+)[​](#touch-devices-v330 "Direct link to Touch Devices (v3.3.0+)") While desktop systems use multiple concurrent prediction strategies, touch devices operate with a single active strategy that can be configured while [initializing](/docs/getting-started/initialize-the-manager.md) the `ForesightManager`. * **onTouchStart** default - Captures the initial touch event to begin prefetching when users start interacting with registered elements * **Viewport Enter** - Detects when registered elements enter the viewport and prefetches their content based on scroll behavior and visibility * **None** - Disables ForesightJS on touch devices (previous behavior) ## Why ForesightJS?[​](#why-foresightjs "Direct link to Why ForesightJS?") ForesightJS is designed for developers who want to squeeze every drop of performance out of their web applications. It's a specialized tool, not something to drop into every project. ### Which problems does ForesightJS solve?[​](#which-problems-does-foresightjs-solve "Direct link to Which problems does ForesightJS solve?") #### Problem 1: On-Hover Prefetching Still Has Latency[​](#problem-1-on-hover-prefetching-still-has-latency "Direct link to Problem 1: On-Hover Prefetching Still Has Latency") Traditional hover-based prefetching only triggers after the user's cursor reaches an element. This approach wastes the critical 100-200ms window between when a user begins moving toward a target and when the hover event actually fires—time that could be used for prefetching. #### Problem 2: Viewport-Based Prefetching is Wasteful[​](#problem-2-viewport-based-prefetching-is-wasteful "Direct link to Problem 2: Viewport-Based Prefetching is Wasteful") Many modern frameworks (like Next.js) automatically prefetch resources for all links that enter the viewport. While well-intentioned, this creates significant overhead since users typically interact with only a small fraction of visible elements. Simply scrolling up and down the Next.js homepage can trigger ***1.59MB*** of unnecessary prefetch requests. #### Problem 3: Hover-Based Prefetching Excludes Keyboard Users[​](#problem-3-hover-based-prefetching-excludes-keyboard-users "Direct link to Problem 3: Hover-Based Prefetching Excludes Keyboard Users") Many routers rely on hover-based prefetching, but this approach completely excludes keyboard users since keyboard navigation never triggers hover events. This means keyboard users miss out on the performance benefits that mouse users get from hover-based prefetching. --- # Your First Element This guide will walk you through registering your first element with `ForesightJS` and understanding how the prediction system works. ## Basic Usage Example[​](#basic-usage-example "Direct link to Basic Usage Example") This basic example is in vanilla JS, ofcourse most people will use ForesightJS with a framework. You can read about framework integrations below. ``` import { ForesightManager } from "js.foresight" // Register an element to be tracked const myLink = document.getElementById("my-link") ForesightManager.instance.register({ element: myLink, callback: () => { // This is where your prefetching logic goes console.log("User is likely to interact with this element!") }, // Optional element settings }) ``` Thats it! ## Provide element settings[​](#provide-element-settings "Direct link to Provide element settings") However if you want to add a bit more power to your element you can give it the following props: ``` import { ForesightManager } from "js.foresight" const myLink = document.getElementById("my-link") ForesightManager.instance.register({ element: myLink, callback: () => { console.log("User is likely to interact with this element!") }, hitSlop: 50, // slop around the element, making its hitbox bigger name: "My Foresight button!", // name visible in the debug tools meta: { route: "/about", }, // your custom meta data for analytics reactivateAfter: 5 * 60 * 1000, // time for the element to reactivate after the callback has been hit }) ``` ## Integrations[​](#integrations "Direct link to Integrations") Since ForesightJS is framework agnostic, it can be integrated with any JavaScript framework. Ready-to-use implementations are available for: * [Next.js](/docs/integrations/react/nextjs.md) * [React Router](/docs/integrations/react/react-router.md) * [Angular](/docs/integrations/angular.md) ## Development Tools[​](#development-tools "Direct link to Development Tools") ForesightJS has dedicated [Development Tools](/docs/debugging/devtools.md) that help you understand and tune how prediction is working in your application: ``` npm install js.foresight-devtools ``` ``` import { ForesightDevtools } from "js.foresight-devtools" // Initialize development tools ForesightDevtools.initialize({ // optional props }) ``` --- # Angular ``` npm install js.foresight ngx-foresight npm install -D js.foresight-devtools # or pnpm add js.foresight js.foresight-devtools ngx-foresight pnpm add -D js.foresight-devtools ``` After that import the `ForesightjsDirective` to the components with `href` and `routerLink`, and use the `ForesightjsStrategy` as `preloadingStrategy` in the router's configuration. For example: ``` import { ForesightManager } from 'js.foresight'; import { ForesightDevtools } from 'js.foresight-devtools'; import { ForesightjsDirective } from 'ngx-foresight'; ForesightManager.initialize({ enableMousePrediction: true, positionHistorySize: 8, trajectoryPredictionTime: 80, defaultHitSlop: 10, enableTabPrediction: true, tabOffset: 3, enableScrollPrediction: true, scrollMargin: 150, }); ForesightDevtools.initialize({ showDebugger: true, isControlPanelDefaultMinimized: true, // optional setting which allows you to minimize the control panel on default showNameTags: true, // optional setting which shows the name of the element sortElementList: 'visibility', // optional setting for how the elements in the control panel are sorted }); ``` ``` ``` ``` // configure preloading strategy as per routes provideRouter(routes, withPreloading(ForesightjsStrategy)), // for older versions RouterModule.forRoot(routes, { preloadingStrategy: ForesightjsStrategy }) ``` --- # Next.js ## Next.js default prefetching[​](#nextjs-default-prefetching "Direct link to Next.js default prefetching") Next.js's default prefetching method prefetches when links enter the viewport, this is a great user experience but can lead to unnecessary data transfer for bigger websites. For example by scrolling down the [Next.js homepage](https://nextjs.org/) it triggers **\~1.59MB** of prefetch requests as every single link on the page gets prefetched, regardless of user intent. To avoid this, we can wrap the `Link` component and add ForesightJS. The official Next.js [prefetching docs](https://nextjs.org/docs/app/guides/prefetching#extending-or-ejecting-link) mention ForesightJS as an example for custom prefetching strategies. ## ForesightLink Component[​](#foresightlink-component "Direct link to ForesightLink Component") Below is an example of creating an wrapper around the Next.js `Link` component that prefetches with ForesightJS. On mobile devices ForesightJS uses the configured [`touchDeviceStrategy`](/docs/configuration/global-settings.md#touch-device-strategy-v330). This implementation uses the `useForesight` react hook which can be found [here](/docs/integrations/react/useForesight.md). ``` "use client" import type { LinkProps } from "next/link" import Link from "next/link" import { type ForesightRegisterOptions } from "js.foresight" import useForesight from "../hooks/useForesight" import { useRouter } from "next/navigation" interface ForesightLinkProps extends Omit, Omit { children: React.ReactNode className?: string } export function ForesightLink({ children, className, ...props }: ForesightLinkProps) { const router = useRouter() // import from "next/navigation" not "next/router" const { elementRef } = useForesight({ callback: () => { router.prefetch(props.href.toString()) }, hitSlop: props.hitSlop, name: props.name, meta: props.meta, reactivateAfter: props.reactivateAfter, }) return ( {children} ) } ``` ## Basic Usage[​](#basic-usage "Direct link to Basic Usage") ``` import ForesightLink from "./ForesightLink" export default function Navigation() { return ( Home ) } ``` caution If you dont see the correct prefetching behaviour make sure you are in production. Next.js only prefetches in production and not in development --- # React Router ## React Router's Prefetching[​](#react-routers-prefetching "Direct link to React Router's Prefetching") React Router DOM (v6.4+) uses no prefetching by default. While you can enable prefetching with options like `intent` (hover/focus) or `viewport`, it doesnt have the same flexibility as ForesightJS. To add ForesightJS to React Router you can create a `ForesightLink` component wrapping the `Link` component. ## ForesightLink Component[​](#foresightlink-component "Direct link to ForesightLink Component") Below is an example of creating an wrapper around the React Router `Link` component that prefetches with ForesightJS. On mobile devices ForesightJS uses the configured [`touchDeviceStrategy`](/docs/configuration/global-settings.md#touch-device-strategy-v330). This implementation uses the `useForesight` react hook which can be found [here](/docs/integrations/react/useForesight.md). ``` "use client" import type { ForesightRegisterOptions } from "js.foresight" import { useState } from "react" import { Link, PrefetchPageLinks, type LinkProps } from "react-router" import useForesight from "./useForesight" interface ForesightLinkProps extends Omit, Omit { children: React.ReactNode className?: string } export function ForesightLink({ children, className, ...props }: ForesightLinkProps) { const [shouldPrefetch, setShouldPrefetch] = useState(false) const { elementRef, registerResults } = useForesight({ callback: () => { setShouldPrefetch(true) }, hitSlop: props.hitSlop, name: props.name, meta: props.meta reactivateAfter: props.reactivateAfter, }) return ( <> {shouldPrefetch && } {children} ) } ``` ### Usage of ForesightLink[​](#usage-of-foresightlink "Direct link to Usage of ForesightLink") ``` export function Navigation() { return ( <> contact about ) } ``` --- # useForesight The `useForesight` hook serves as the base for all ForesightJS usage with any React framework. ## useForesight[​](#useforesight-1 "Direct link to useForesight") ``` import { useRef, useEffect } from "react" import { ForesightManager, type ForesightRegisterOptionsWithoutElement, type ForesightRegisterResult, } from "js.foresight" export default function useForesight( options: ForesightRegisterOptionsWithoutElement ) { const elementRef = useRef(null) const registerResults = useRef(null) useEffect(() => { if (!elementRef.current) return registerResults.current = ForesightManager.instance.register({ element: elementRef.current, ...options, }) }, [options]) return { elementRef, registerResults } } ``` ### Return Values[​](#return-values "Direct link to Return Values") The hook returns an object containing: * `elementRef` - To attach to your target element * [`registerResults`](/docs/configuration/element-settings.md#return-properties) - Registration details like `isRegistered` **Important:** Due to React's rendering lifecycle, both `elementRef` and `registerResults` will be `null` during the initial render. The element gets registered only after the component mounts and the ref is attached. This means while implementing fallback prefetching logic, don't check if `registerResults` is `null`. Instead, always check the registration status using `registerResults.isRegistered` or device capabilities like `registerResults.isTouchDevice` and `registerResults.isLimitedConnection`. ### Basic Usage[​](#basic-usage "Direct link to Basic Usage") ``` import useForesight from "./useForesight" function MyComponent() { const { elementRef, registerResults } = useForesight({ callback: () => { console.log("Prefetching data...") // Your prefetch logic here }, hitSlop: 10, name: "my-button", }) return } ``` ### Framework Integrations[​](#framework-integrations "Direct link to Framework Integrations") For ready-to-use components built on top of useForesight, see our framework-specific integrations: * [React Router](/docs/integrations/react/react-router.md#foresightlink-component) * [Next.js](/docs/integrations/react/nextjs.md#foresightlink-component) --- # Vue Vue integration examples coming soon. ---