Faster subsequent page-loads by prefetching in-viewport links during idle time
Quicklink attempts to make navigations to subsequent pages load faster. It:
- Detects links within the viewport (using Intersection Observer)
- Waits until the browser is idle (using requestIdleCallback)
- Checks if the user isn't on a slow connection (using
navigator.connection.effectiveType
) or has data-saver enabled (usingnavigator.connection.saveData
) - Prefetches URLs to the links (using
<link rel=prefetch>
or XHR). Provides some control over the request priority (can switch tofetch()
if supported).
This project aims to be a drop-in solution for sites to prefetch links based on what is in the user's viewport. It also aims to be small (< 1KB minified/gzipped).
npm install --save quicklink
You can also grab quicklink
from unpkg.com/quicklink.
Once initialized, quicklink
will automatically prefetch URLs for links that are in-viewport during idle time.
Quickstart:
<!-- Include quicklink from dist -->
<script src="dist/quicklink.js"></script>
<!-- Initialize (you can do this whenever you want) -->
<script>
quicklink();
</script>
For example, you can initialize after the load
event fires:
<script>
window.addEventListener('load', () =>{
quicklink();
});
</script>
ES Module import:
import quicklink from "dist/quicklink.mjs";
quicklink();
The above options are best for multi-page sites. Single-page apps have a few options available for using quicklink with a router:
- Call
quicklink()
once a navigation to a new route has completed - Call
quicklink()
against a specific DOM element / component - Call
quicklink({urls:[...]})
with a custom set of URLs to prefetch
quicklink
accepts an optional options object with the following parameters:
el
: DOM element to observe for in-viewport links to prefetchurls
: Static array of URLs to prefetch (instead of observingdocument
or a DOM element links in the viewport)timeout
: Integer for therequestIdleCallback
timeout. A time in milliseconds by which the browser must execute prefetching. Defaults to 2 seconds.timeoutFn
: Function for specifying a timeout. Defaults torequestIdleCallback
. Can also be swapped out for a custom function like networkIdleCallback (see demos)priority
: Boolean specifying preferred priority for fetches. Defaults tofalse
.true
will attempt to use thefetch()
API where supported (rather than rel=prefetch)origins
: Static array of URL hostname strings that are allowed to be prefetched. Defaults to the same domain origin, which prevents any cross-origin requests.ignores
: A RegExp, Function, or Array that further determines if a URL should be prefetched. These execute after origin matching.
TODO:
- Explore detecting file-extension of resources and using rel=preload for high priority fetches
- Explore using Priority Hints for importance hinting
quicklink
:
- Includes a very small fallback for requestIdleCallback
- Requires
IntersectionObserver
to be supported (see CanIUse). We recommend conditionally polyfilling this feature with a service like Polyfill.io:
<script src="https://polyfill.io/v2/polyfill.min.js?features=IntersectionObserver"></script>
Alternatively, see the Intersection Observer polyfill.
Defaults to 2 seconds (via requestIdleCallback
). Here we override it to 4 seconds:
quicklink({
timeout: 4000
});
Defaults to document
otherwise.
const elem = document.getElementById('carousel');
quicklink({
el: elem
});
If you would prefer to provide a static list of URLs to be prefetched, instead of detecting those in-viewport, customizing URLs is supported.
quicklink({
urls: ['2.html','3.html', '4.js']
});
Defaults to low-priority (rel=prefetch
or XHR). For high-priority (priority: true
), attempts to use fetch()
or falls back to XHR.
quicklink({ priority: true });
Provide a list of hostnames that should be prefetch-able. Only the same origin is allowed by default.
Important: You must also include your own hostname!
quicklink({
origins: [
// add mine
'my-website.com',
'api.my-website.com',
// add third-parties
'other-website.com',
'example.com',
// ...
]
});
Enables all cross-origin requests to be made.
quicklink({
origins: true,
// or
origins: []
});
These filters run after the origins
matching has run. Ignores can be useful for avoiding large file downloads or for responding to DOM attributes dynamically.
// Same-origin restraint is enabled by default.
//
// This example will ignore all requests to:
// - all "/api/*" pathnames
// - all ".zip" extensions
// - all <a> tags with "noprefetch" attribute
//
quicklink({
ignores: [
/\/api\/?/,
uri => uri.includes('.zip'),
(uri, elem) => elem.hasAttribute('noprefetch')
]
});
You may also wish to ignore prefetches to URLs which contain a URL fragment (e.g. index.html#top
). This can be useful if you (1) are using anchors to headings in a page or (2) have URL fragments setup for a single-page application, and which to avoid firing prefetches for similar URLs.
Using ignores
this can be achieved as follows:
quicklink({
ignores: [
uri => uri.includes('#')
// or RegExp: /#(.+)/
// or element matching: (uri, elem) => !!elem.hash
]
});
The prefetching provided by quicklink
can be viewed as a progressive enhancement. Cross-browser support is as follows:
- Without polyfills: Chrome, Firefox, Edge, Opera, Android Browser, Samsung Internet.
- With Intersection Observer polyfill ~6KB gzipped/minified: Safari, IE11
- With the above and a Set() and Array.from polyfill: IE9 and IE10. Core.js provides both
Set()
andArray.from()
shims. Projects like es6-shim are an alternative you can consider.
Certain features have layered support:
- The Network Information API, which is used to check if the user has a slow effective connection type (via
navigator.connection.effectiveType
) is only available in Chrome 61+ and Opera 57+ - If opting for
{priority: true}
and the Fetch API isn't available, XHR will be used instead.
quicklink
includes a prefetcher that can be individually imported for use in other projects. After installing quicklink
as a dependency, you can use it as follows:
<script type="module">
import prefetch from '../src/prefetch.mjs';
const urls = ['1.html', '2.html'];
const promises = urls.map(url => prefetch(url));
Promise.all(promises);
</script>
Here's a WebPageTest run for our demo improving page-load performance by up to 4 seconds via quicklink's prefetching. A video comparison of the before/after prefetching is on YouTube.
For demo purposes, we deployed a version of the Google Blog on Firebase hosting. We then deployed another version of it, adding quicklink to the homepage and benchmarked navigating from the homepage to an article that was automatically prefetched. The prefetched version loaded faster.
Please note: this is by no means an exhaustive benchmark of the pros and cons of in-viewport link prefetching. Just a demo of the potential improvements the approach can offer. Your own mileage may heavily vary.
- Using Gatsby? You already get most of this for free baked in. It uses
Intersection Observer
to prefetch all of the links that are in view and provided heavy inspiration for this project. - Want a more data-driven approach? See Guess.js. It uses analytics and machine-learning to prefetch resources based on how users navigate your site. It also has plugins for Webpack and Gatsby.
Licensed under the Apache-2.0 license.