HTML performance prefetch prefetcher JavaScript CSS Shell speed web-performance
Need help with quicklink?
Click the “chat” button below for chat support from the developer who created it, or find similar developers for support.
GoogleChromeLabs

Description

⚡️Faster subsequent page-loads by prefetching in-viewport links during idle time

8.6K Stars 326 Forks Apache License 2.0 254 Commits 30 Opened issues

Services available

Need anything else?

quicklink
npm gzip size downloads travis

quicklink

Faster subsequent page-loads by prefetching in-viewport links during idle time

How it works

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 (using
    navigator.connection.saveData
    )
  • Prefetches URLs to the links (using
    or XHR). Provides some control over the request priority (can switch to
    fetch()
    if supported).

Why

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).

Multi page apps

Installation

For use with node and npm:

npm install --save quicklink

You can also grab

quicklink
from unpkg.com/quicklink.

Usage

Once initialized,

quicklink
will automatically prefetch URLs for links that are in-viewport during idle time.

Quickstart:


For example, you can initialize after the

load
event fires:

ES Module import:

import { listen, prefetch } from "quicklink";

Single page apps (React)

Installation

First, install the packages with node and npm:

npm install quicklink webpack-route-manifest --save-dev

Then, configure Webpack route manifest into your project, as explained here. This will generate a map of routes and chunks called

rmanifest.json
. It can be obtained at:
  • URL:
    site_url/rmanifest.json
  • Window object:
    window.__rmanifest

Usage

Import

quicklink
React HOC where want to add prefetching functionality. Wrap your routes with the
withQuicklink()
HOC.

Example:

import { withQuicklink } from 'quicklink/dist/react/hoc.js';

const options = { origins: [] };

Loading...}>

API

quicklink.listen(options)

Returns:

Function

A "reset" function is returned, which will empty the active

IntersectionObserver
and the cache of URLs that have already been prefetched. This can be used between page navigations and/or when significant DOM changes have occurred.

options.el

Type:

HTMLElement

Default:
document.body

The DOM element to observe for in-viewport links to prefetch.

options.limit

Type:

Number

Default:
Infinity

The total requests that can be prefetched while observing the

options.el
container.

options.throttle

Type:

Number

Default:
Infinity

The concurrency limit for simultaneous requests while observing the

options.el
container.

options.timeout

Type:

Number

Default:
2000

The

requestIdleCallback
timeout, in milliseconds.

Note: The browser must be idle for the configured duration before prefetching.

options.timeoutFn

Type:

Function

Default:
requestIdleCallback

A function used for specifying a

timeout
delay.
This can be swapped out for a custom function like networkIdleCallback (see demos).

By default, this uses

requestIdleCallback
or the embedded polyfill.

options.priority

Type:

Boolean

Default:
false

Whether or not the URLs within the

options.el
container should be treated as high priority.

When

true
, quicklink will attempt to use the
fetch()
API if supported (rather than
link[rel=prefetch]
).

options.origins

Type:

Array

Default:
[location.hostname]

A static array of URL hostnames that are allowed to be prefetched.
Defaults to the same domain origin, which prevents any cross-origin requests.

Important: An empty array (

[]
) allows all origins to be prefetched.

options.ignores

Type:

RegExp
or
Function
or
Array

Default:
[]

Determine if a URL should be prefetched.

When a

RegExp
tests positive, a
Function
returns
true
, or an
Array
contains the string, then the URL is not prefetched.

Note: An

Array
may contain
String
,
RegExp
, or
Function
values.

Important: This logic is executed after origin matching!

options.onError

Type:

Function

Default: None

An optional error handler that will receive any errors from prefetched requests.
By default, these errors are silently ignored.

options.hrefFn

Type:

Function

Default: None

An optional function to generate the URL to prefetch. It receives an Element as the argument.

quicklink.prefetch(urls, isPriority)

Returns:

Promise

The

urls
provided are always passed through
Promise.all
, which means the result will always resolve to an Array.

Important: You much

catch
you own request error(s).

urls

Type:

String
or
Array

Required:
true

One or many URLs to be prefetched.

Note: Each

url
value is resolved from the current location.

isPriority

Type:

Boolean

Default:
false

Whether or not the URL(s) should be treated as "high priority" targets.
By default, calls to

prefetch()
are low priority.

Note: This behaves identically to

listen()
's
priority
option.

Polyfills

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:

Alternatively, see the Intersection Observer polyfill.

Recipes

Set a custom timeout for prefetching resources

Defaults to 2 seconds (via

requestIdleCallback
). Here we override it to 4 seconds:
quicklink.listen({
  timeout: 4000
});

Set the DOM element to observe for in-viewport links

Defaults to

document
otherwise.
quicklink.listen({
  el: document.getElementById('carousel')
});

Programmatically
prefetch()
URLs

If you would prefer to provide a static list of URLs to be prefetched, instead of detecting those in-viewport, customizing URLs is supported.

// Single URL
quicklink.prefetch('2.html');

// Multiple URLs quicklink.prefetch(['2.html', '3.html', '4.js']);

// Multiple URLs, with high priority // Note: Can also be use with single URL! quicklink.prefetch(['2.html', '3.html', '4.js'], true);

Set the request priority for prefetches while scrolling

Defaults to low-priority (

rel=prefetch
or XHR). For high-priority (
priority: true
), attempts to use
fetch()
or falls back to XHR.

Note: This runs

prefetch(..., true)
with URLs found within the
options.el
container.
quicklink.listen({ priority: true });

Specify a custom list of allowed origins

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.listen({
  origins: [
    // add mine
    'my-website.com',
    'api.my-website.com',
    // add third-parties
    'other-website.com',
    'example.com',
    // ...
  ]
});

Allow all origins

Enables all cross-origin requests to be made.

Note: You may run into CORB and CORS issues!

quicklink.listen({
  origins: true,
  // or
  origins: []
});

Custom Ignore Patterns

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  tags with "noprefetch" attribute
//
quicklink.listen({
  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.listen({
  ignores: [
    uri => uri.includes('#')
    // or RegExp: /#(.+)/
    // or element matching: (uri, elem) => !!elem.hash
  ]
});

Custom URL to prefetch via hrefFn callback

The hrefFn method allows to build the URL to prefetch (e.g. API endpoint) on the fly instead of the prefetching the

href
attribute URL.
quicklink.listen({
  hrefFn: function(element) {
    return element.href.replace('html','json');
  }
});

Browser Support

The prefetching provided by

quicklink
can be viewed as a progressive enhancement. Cross-browser support is as follows:
  • Without polyfills: Chrome, Safari ≥ 12.1, Firefox, Edge, Opera, Android Browser, Samsung Internet.
  • With Intersection Observer polyfill ~6KB gzipped/minified: Safari ≤ 12.0, IE11
  • With the above and a Set() and Array.from polyfill: IE9 and IE10. Core.js provides both
    Set()
    and
    Array.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.

Using the prefetcher directly

A

prefetch
method can be individually imported for use in other projects.
This method includes the logic to respect Data Saver and 2G connections. It also issues requests thru
fetch()
, XHRs, or
link[rel=prefetch]
depending on (a) the
isPriority
value and (b) the current browser's support.

After installing

quicklink
as a dependency, you can use it as follows:

Demo

Glitch demos

Research

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.

Additional notes

Session Stitching

Cross-origin prefetching (e.g a.com/foo.html prefetches b.com/bar.html) has a number of limitations. One such limitation is with session-stitching. b.com may expect a.com's navigation requests to include session information (e.g a temporary ID - e.g b.com/bar.html?hash=<>&timestamp=<>), where this information is used to customize the experience or log information to analytics. If session-stitching requires a timestamp in the URL, what is prefetched and stored in the HTTP cache may not be the same as the one the user ultimately navigates to. This introduces a challenge as it can result in double prefetches.

To workaround this problem, you can consider passing along session information via the ping attribute (separately) so the origin can stitch a session together asynchronously.

Ad-related considerations

Sites that rely on ads as a source of monetization should not prefetch ad-links, to avoid unintentionally counting clicks against those ad placements, which can lead to inflated Ad CTR (click-through-rate).

Ads appear on sites mostly in two ways:

  • Inside iframes: By default, most ad-servers render ads within iframes. In these cases, those ad-links won't be prefetched by Quicklink, unless a developer explicitly passes in the URL of an ads iframe. The reason is that the library look-up for in-viewport elements is restricted to those of the top-level origin.

  • Outside iframes:: In cases when the site shows same-origin ads, displayed in the top-level document (e.g. by hosting the ads themselves and by displaying the ads in the page directly), the developer needs to explicitly tell Quicklink to avoid prefetching these links. This can be achieved by passing the URL or subpath of the ad-link, or the element containing it to the custom ignore patterns list.

Related projects

  • 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.
  • WordPress users can now get quicklink as a WordPress Plugin from the plugin repository.
  • Drupal users can install the Quicklink Drupal module.
  • Want less aggressive prefetching? instant.page prefetches on mouseover and touchstart, right before a click.

License

Licensed under the Apache-2.0 license.

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.