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.
- 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-srcpattern - 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 | 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.
<script src="https://cdn.jsdelivr.net/gh/lazaroagomez/SmartPrefetch@latest/smartprefetch.prod.min.js"></script>
<script>
SmartPrefetch.listen();
</script>Download smartprefetch.min.js and include it:
<script src="smartprefetch.min.js"></script>
<script>
SmartPrefetch.listen();
</script><script src="smartprefetch.min.js"></script>
<script>
SmartPrefetch.listen();
</script>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}`)
});// Single URL
SmartPrefetch.prefetch('/page.html');
// Multiple URLs
SmartPrefetch.prefetch(['/page1.html', '/page2.html', '/page3.html']);// Prerender requires Speculation Rules API and same-origin URLs
SmartPrefetch.prerender('/important-page.html');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
// }SmartPrefetch.listen({
lazyImages: true,
lazyImagesSelector: 'img[data-src]',
lazyImagesRootMargin: '200px'
});HTML setup for lazy images:
<img data-src="image.jpg" alt="Lazy loaded image">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.
SmartPrefetch.stop();Start listening for prefetch triggers. Returns nothing.
Stop listening and clean up event listeners, observers, and speculation rules.
Manually prefetch one or more URLs. Returns a Promise that resolves when all prefetches complete.
urls- String or array of strings
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
Get current statistics and state. Returns an object with:
prefetched- Number of URLs prefetchedprerendered- Number of URLs prerenderedhits- Number of prefetched URLs that were navigated toerrors- Number of prefetch errorsskipped- Object with skip reasons (duplicate, network, limit, ignored, invalid)prefetchedUrls- Array of prefetched URL stringsprerenderedUrls- Array of prerendered URL stringssupport- Object with browser feature support flagsisListening- Whether SmartPrefetch is currently active
| 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 |
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=')
]
});-
Hover Detection: When user hovers over a link, a 65ms timer starts. If mouse leaves before timer fires, prefetch is cancelled.
-
Touch Detection: On touch devices, prefetch triggers immediately on
touchstart. A 2500ms window prevents duplicate prefetch from the subsequent mouse events. -
Viewport Detection (opt-in): Uses IntersectionObserver to prefetch links as they enter the viewport.
-
Prefetch Method Selection:
- If Speculation Rules API is supported and URL is same-origin: use Speculation Rules
- Otherwise: fall back to
<link rel="prefetch">
-
Network Awareness: Checks
navigator.connectionfor Save Data mode and slow connections. Also pauses when page is hidden. -
Service Worker Handling: Detects active service workers that might intercept navigation requests. When detected, automatically falls back to link prefetch to avoid 503 errors.
-
Start a local server from the project root:
python -m http.server 8000
-
Open the demo at
http://localhost:8000/docs/ -
Open DevTools (F12) → Network tab → Filter by "Doc" to see prefetch requests
| 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 |
-
Save Data Mode:
- Chrome DevTools → Network tab → Check "Disable cache"
- Go to
chrome://flags→ Search "Save Data" → Enable - Reload page - prefetching should be skipped
-
Slow Network:
- DevTools → Network tab → Throttling dropdown → Select "Slow 3G" or "Fast 3G"
- Note: 2G throttling will disable prefetching
-
Page Visibility:
- Switch to another tab while hovering - prefetch should cancel
- Return to tab - prefetching resumes
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 })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
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 methoduseSpeculationRules: false- Always use link prefetch (like instant.page)useSpeculationRules: true- Always use Speculation Rules (ignore service workers)
| 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 |
To create minified versions:
npm install -g terser
npm run buildOr 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 falseMIT License - see LICENSE file.