Skip to content

lazaroagomez/SmartPrefetch

Repository files navigation

SmartPrefetch

A modern, intelligent prefetching library that makes your website feel instant. Uses the Speculation Rules API as primary mechanism with <link rel="prefetch"> fallback for maximum browser compatibility.

Features

  • Speculation Rules API - Uses modern browser APIs for optimal prefetching/prerendering
  • Automatic Fallback - Falls back to <link rel="prefetch"> for older browsers
  • Service Worker Detection - Auto-detects active service workers and uses link prefetch to avoid 503 errors
  • Touch/Mouse Disambiguation - Smart 2500ms window prevents double prefetches on hybrid devices
  • Configurable Hover Delay - 65ms default delay prevents unnecessary prefetches from cursor fly-overs
  • Hover Cancellation - Moving mouse away cancels pending prefetch
  • Viewport Detection - Optional IntersectionObserver-based prefetching for visible links
  • Lazy Image Loading - Opt-in lazy loading for images with data-src pattern
  • Content Visibility - Opt-in content-visibility optimization for below-fold elements
  • Network Awareness - Respects Save Data mode and slow 2G connections
  • Flexible Ignore Patterns - RegExp, functions, or string matching
  • Deduplication - Never prefetches the same URL twice
  • Configurable Limits - Control max prefetches/prerenders
  • Statistics Tracking - Monitor prefetch hits and performance
  • Zero Dependencies - Single ~9KB file, no build process required

Browser Support

Browser Speculation Rules Link Prefetch
Chrome 109+ Yes Yes
Edge 109+ Yes Yes
Firefox No Yes
Safari No Partial

SmartPrefetch automatically uses the best available method.

Installation

CDN (Recommended)

<script src="https://cdn.jsdelivr.net/gh/lazaroagomez/SmartPrefetch@latest/smartprefetch.prod.min.js"></script>
<script>
  SmartPrefetch.listen();
</script>

Self-hosted

Download smartprefetch.min.js and include it:

<script src="smartprefetch.min.js"></script>
<script>
  SmartPrefetch.listen();
</script>

Usage

Basic

<script src="smartprefetch.min.js"></script>
<script>
  SmartPrefetch.listen();
</script>

With Options

SmartPrefetch.listen({
  // Hover/Touch
  hoverDelay: 65,        // ms to wait before prefetching on hover
  enableHover: true,     // enable hover-triggered prefetch
  enableTouch: true,     // enable touch-triggered prefetch

  // Viewport (disabled by default)
  enableViewport: false, // enable viewport-triggered prefetch
  viewportThreshold: 0,  // IntersectionObserver threshold
  viewportMargin: '0px', // IntersectionObserver rootMargin

  // Limits
  maxPrefetches: 10,     // max URLs to prefetch
  maxPrerenders: 1,      // max URLs to prerender

  // URL Filtering
  ignores: [
    '/logout',
    '/admin',
    /private/,
    (url, el) => el.hasAttribute('data-no-prefetch')
  ],
  allowQueryString: false,   // prefetch URLs with query strings?
  allowExternalLinks: false, // prefetch external links?

  // Network
  respectSaveData: true,     // skip if Save Data enabled
  respectSlowNetwork: true,  // skip on 2G connections
  respectVisibility: true,   // skip when page hidden

  // Debug
  debug: false,              // log to console
  trackStats: true,          // track hit statistics

  // Callbacks
  onPrefetch: (url, method) => console.log(`Prefetched: ${url}`),
  onError: (url, error) => console.error(`Error: ${url}`, error),
  onHit: (url) => console.log(`Cache hit: ${url}`)
});

Manual Prefetch

// Single URL
SmartPrefetch.prefetch('/page.html');

// Multiple URLs
SmartPrefetch.prefetch(['/page1.html', '/page2.html', '/page3.html']);

Manual Prerender

// Prerender requires Speculation Rules API and same-origin URLs
SmartPrefetch.prerender('/important-page.html');

Get Statistics

const stats = SmartPrefetch.getStats();
console.log(stats);
// {
//   prefetched: 5,
//   prerendered: 0,
//   hits: 2,
//   errors: 0,
//   skipped: { duplicate: 1, network: 0, limit: 0, ignored: 2, invalid: 0 },
//   lazyLoaded: 3,
//   contentVisibilityApplied: 8,
//   prefetchedUrls: [...],
//   prerenderedUrls: [...],
//   support: { speculationRules: true, linkPrefetch: true, nativeLazyLoading: true, contentVisibility: true, ... },
//   isListening: true
// }

Lazy Image Loading

SmartPrefetch.listen({
  lazyImages: true,
  lazyImagesSelector: 'img[data-src]',
  lazyImagesRootMargin: '200px'
});

HTML setup for lazy images:

<img data-src="image.jpg" alt="Lazy loaded image">

Content Visibility Optimization

SmartPrefetch.listen({
  contentVisibility: true,
  contentVisibilitySelector: 'section, article, .card',
  contentVisibilitySize: '500px'
});

This applies content-visibility: auto to elements below 1.5x viewport height, improving initial render performance.

Stop Listening

SmartPrefetch.stop();

API Reference

SmartPrefetch.listen(options?)

Start listening for prefetch triggers. Returns nothing.

SmartPrefetch.stop()

Stop listening and clean up event listeners, observers, and speculation rules.

SmartPrefetch.prefetch(urls)

Manually prefetch one or more URLs. Returns a Promise that resolves when all prefetches complete.

  • urls - String or array of strings

SmartPrefetch.prerender(urls)

Manually prerender one or more URLs using Speculation Rules API. Returns array of booleans indicating success.

  • urls - String or array of strings
  • Only works for same-origin URLs
  • Requires browser support for Speculation Rules API

SmartPrefetch.getStats()

Get current statistics and state. Returns an object with:

  • prefetched - Number of URLs prefetched
  • prerendered - Number of URLs prerendered
  • hits - Number of prefetched URLs that were navigated to
  • errors - Number of prefetch errors
  • skipped - Object with skip reasons (duplicate, network, limit, ignored, invalid)
  • prefetchedUrls - Array of prefetched URL strings
  • prerenderedUrls - Array of prerendered URL strings
  • support - Object with browser feature support flags
  • isListening - Whether SmartPrefetch is currently active

Options Reference

Option Type Default Description
hoverDelay number 65 Milliseconds to wait before prefetching on hover
enableHover boolean true Enable hover-triggered prefetch
enableTouch boolean true Enable touch-triggered prefetch
enableViewport boolean false Enable viewport-triggered prefetch
viewportThreshold number 0 IntersectionObserver threshold (0-1)
viewportMargin string '0px' IntersectionObserver rootMargin
maxPrefetches number 10 Maximum URLs to prefetch
maxPrerenders number 1 Maximum URLs to prerender
ignores array [] Patterns to ignore (RegExp, function, string)
allowQueryString boolean false Allow prefetching URLs with query strings
allowExternalLinks boolean false Allow prefetching external links
speculationMode string 'prefetch' Default mode: 'prefetch' or 'prerender'
useSpeculationRules string/boolean 'auto' true, false, or 'auto' (auto disables when service worker active)
eagerness string 'moderate' Speculation Rules eagerness
respectSaveData boolean true Skip if Save Data is enabled
respectSlowNetwork boolean true Skip on 2G/slow-2g connections
respectVisibility boolean true Skip when page is hidden
fetchPriority string 'low' Priority for viewport/manual prefetch
hoverPriority string 'high' Priority for hover/touch prefetch
debug boolean false Log debug messages to console
trackStats boolean true Track navigation hits
onPrefetch function null Called when URL is prefetched
onError function null Called on prefetch error
onHit function null Called when prefetched URL is visited
lazyImages boolean false Enable lazy loading for images
lazyImagesSelector string 'img[data-src]' Selector for lazy images
lazyImagesRootMargin string '200px' IntersectionObserver margin for lazy images
contentVisibility boolean false Enable content-visibility optimization
contentVisibilitySelector string 'section, article, .card' Selector for content-visibility elements
contentVisibilitySize string '500px' contain-intrinsic-size value

Ignore Patterns

The ignores array accepts multiple pattern types:

SmartPrefetch.listen({
  ignores: [
    // String - matches if URL contains string
    '/logout',
    '/admin',

    // RegExp - matches against full URL
    /\/api\//,
    /\.pdf$/,

    // Function - receives (url, element), return true to ignore
    (url, el) => el.hasAttribute('data-no-prefetch'),
    (url) => url.includes('token=')
  ]
});

How It Works

  1. Hover Detection: When user hovers over a link, a 65ms timer starts. If mouse leaves before timer fires, prefetch is cancelled.

  2. Touch Detection: On touch devices, prefetch triggers immediately on touchstart. A 2500ms window prevents duplicate prefetch from the subsequent mouse events.

  3. Viewport Detection (opt-in): Uses IntersectionObserver to prefetch links as they enter the viewport.

  4. Prefetch Method Selection:

    • If Speculation Rules API is supported and URL is same-origin: use Speculation Rules
    • Otherwise: fall back to <link rel="prefetch">
  5. Network Awareness: Checks navigator.connection for Save Data mode and slow connections. Also pauses when page is hidden.

  6. Service Worker Handling: Detects active service workers that might intercept navigation requests. When detected, automatically falls back to link prefetch to avoid 503 errors.

Testing Guide

Running the Demo

  1. Start a local server from the project root:

    python -m http.server 8000
  2. Open the demo at http://localhost:8000/docs/

  3. Open DevTools (F12) → Network tab → Filter by "Doc" to see prefetch requests

Test Scenarios

Test How to Verify
Hover prefetch Hover over a blue link for 65ms+, see prefetch in Network tab
Hover cancel Hover briefly (<65ms) and move away, no prefetch should occur
Touch prefetch On mobile/touch device, tap a link - immediate prefetch
Ignore patterns Hover over gray "Logout" or "Admin" links - no prefetch
Query strings Hover over yellow links - no prefetch (disabled by default)
External links Hover over green external links - no prefetch (disabled by default)
Duplicate prevention Hover same link twice - only prefetches once
Max limit Prefetch 10+ links - stops after limit reached
Manual prefetch Click "Manual Prefetch" button, check Network tab
Manual prerender Click "Manual Prerender" button, check Network tab
Statistics Click "Refresh Stats" to see prefetch counts

Testing Network Conditions

  1. Save Data Mode:

    • Chrome DevTools → Network tab → Check "Disable cache"
    • Go to chrome://flags → Search "Save Data" → Enable
    • Reload page - prefetching should be skipped
  2. Slow Network:

    • DevTools → Network tab → Throttling dropdown → Select "Slow 3G" or "Fast 3G"
    • Note: 2G throttling will disable prefetching
  3. Page Visibility:

    • Switch to another tab while hovering - prefetch should cancel
    • Return to tab - prefetching resumes

Console Commands

Open browser console and try:

// Check current stats
SmartPrefetch.getStats()

// Manual prefetch
SmartPrefetch.prefetch('/docs/page1.html')

// Manual prerender (Chrome only)
SmartPrefetch.prerender('/docs/page2.html')

// Stop all prefetching
SmartPrefetch.stop()

// Restart with debug mode
SmartPrefetch.listen({ debug: true })

Expected Debug Output

With debug: true, console shows:

[SmartPrefetch] Starting with options: {...}
[SmartPrefetch] Browser support: {speculationRules: true, ...}
[SmartPrefetch] Listening for prefetch triggers
[SmartPrefetch] Prefetching: http://localhost:8000/docs/page1.html
[SmartPrefetch] Already prefetched: http://localhost:8000/docs/page1.html
[SmartPrefetch] Ignored by pattern: http://localhost:8000/docs/logout.html

Service Worker Compatibility

Some websites use service workers that intercept navigation requests (e.g., XenForo forums behind Cloudflare). The Speculation Rules API can cause 503 errors in these environments.

SmartPrefetch automatically detects active service workers and falls back to <link rel="prefetch">:

  • useSpeculationRules: 'auto' (default) - Auto-detect and use best method
  • useSpeculationRules: false - Always use link prefetch (like instant.page)
  • useSpeculationRules: true - Always use Speculation Rules (ignore service workers)

Build Versions

File Size Debug Use Case
smartprefetch.prod.min.js ~2.5KB No Production (recommended)
smartprefetch.min.js ~3KB Yes Development/debugging
smartprefetch.js ~9KB Yes Source code

Building

To create minified versions:

npm install -g terser
npm run build

Or manually:

# Development build (with debug)
terser smartprefetch.js -o smartprefetch.min.js -c -m

# Production build (no debug, no console)
terser smartprefetch.js -o smartprefetch.prod.min.js -c "pure_funcs=['log','warn'],passes=3,drop_console=true" -m --comments false

License

MIT License - see LICENSE file.

About

A modern, intelligent prefetching library that makes your website feel instant.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors