• Stars
    star
    10,907
  • Rank 2,907 (Top 0.06 %)
  • Language
    JavaScript
  • License
    Apache License 2.0
  • Created over 5 years ago
  • Updated 21 days ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

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


npm gzip size ci

quicklink

Faster subsequent page-loads by prefetching or prerendering 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 (using <link rel=prefetch> or XHR) or prerenders (using Speculation Rules API) URLs to the links. 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 or prerender links based on what is in the user's viewport. It also aims to be small (< 2KB minified/gzipped).

Multi page apps

Installation

For use with Node.js and npm:

npm install 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:

<!-- Include quicklink from dist -->
<script src="dist/quicklink.umd.js"></script>
<!-- Initialize (you can do this whenever you want) -->
<script>
  quicklink.listen();
</script>

For example, you can initialize after the load event fires:

<script>
  window.addEventListener('load', () => {
    quicklink.listen();
  });
</script>

ES Module import:

import {listen, prefetch} from 'quicklink';

Single page apps (React)

Installation

First, install the packages with Node.js 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: [],
};

<Suspense fallback={<div>Loading...</div>}>
  <Route path='/' exact component={withQuicklink(Home, options)} />
  <Route path='/blog' exact component={withQuicklink(Blog, options)} />
  <Route path='/blog/:title' component={withQuicklink(Article, options)} />
  <Route path='/about' exact component={withQuicklink(About, options)} />
</Suspense>;

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 or prerendered. This can be used between page navigations and/or when significant DOM changes have occurred.

options.prerender

  • Type: Boolean
  • Default: false

Whether to switch from the default prefetching mode to the prerendering mode for the links inside the viewport.

Note: The prerendering mode (when this option is set to true) will fallback to the prefetching mode if the browser does not support prerender.

options.prerenderAndPrefetch

  • Type: Boolean
  • Default: false

Whether to activate both the prefetching and prerendering mode at the same time.

options.delay

  • Type: Number
  • Default: 0

The amount of time each link needs to stay inside the viewport before being prefetched, in milliseconds.

options.el

  • Type: HTMLElement|NodeList<A>
  • Default: document.body

The DOM element to observe for in-viewport links to prefetch or the NodeList of Anchor Elements.

options.limit

  • Type: Number
  • Default: Infinity

The total requests that can be prefetched or prerendered while observing the options.el container.

options.threshold

  • Type: Number
  • Default: 0

The area percentage of each link that must have entered the viewport to be fetched, in its decimal form (e.g. 0.25 = 25%).

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<String>
  • 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<String>
  • 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.

quicklink.prerender(urls)

Returns: Promise

Important: You much catch you own request error(s).

urls

  • Type: String or Array<String>
  • Required: true

One or many URLs to be prerendered.

Note: Speculative Rules API supports same-site cross origin Prerendering with opt-in header.

Polyfills

quicklink:

  • Includes a very small fallback for requestIdleCallback
  • Requires IntersectionObserver to be supported (see Can I Use). We recommend conditionally polyfilling this feature with a service like Polyfill.io:
<script src="https://polyfill.io/v3/polyfill.min.js?features=IntersectionObserver"></script>

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 a specific Anchor Elements NodeList to observe for in-viewport links

Defaults to document otherwise.

quicklink.listen({
  el: document.querySelectorAll('a.linksToPrefetch'),
});

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

Programmatically prerender() URLs

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

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

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

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 <a> 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(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:

<script type="module">
  import {prefetch} from 'quicklink';
  prefetch(['1.html', '2.html']).catch(error => {
    // Handle own errors
  });
</script>

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

License

Licensed under the Apache-2.0 license.

More Repositories

1

squoosh

Make images smaller using best-in-class codecs, right in the browser.
TypeScript
20,633
star
2

ndb

ndb is an improved debugging experience for Node.js, enabled by Chrome DevTools
JavaScript
10,914
star
3

comlink

Comlink makes WebWorkers enjoyable.
TypeScript
10,702
star
4

carlo

Web rendering surface for Node applications
JavaScript
9,326
star
5

ProjectVisBug

FireBug for designers › Edit any webpage, in any state https://a.nerdy.dev/gimme-visbug
JavaScript
5,321
star
6

sw-precache

[Deprecated] A node module to generate service worker code that will precache specific resources so they work offline.
JavaScript
5,236
star
7

react-adaptive-hooks

Deliver experiences best suited to a user's device and network constraints
JavaScript
5,068
star
8

ui-element-samples

A collection of prototyped UI elements
JavaScript
4,101
star
9

sw-toolbox

[Deprecated] A collection of service worker tools for offlining runtime requests
JavaScript
3,621
star
10

webpack-libs-optimizations

Using a library in your webpack project? Here’s how to optimize it
3,356
star
11

critters

🦔 A Webpack plugin to inline your critical CSS and lazy-load the rest.
JavaScript
3,330
star
12

psi

PageSpeed Insights Reporting for Node
JavaScript
3,103
star
13

lighthousebot

Run Lighthouse in CI, as a web service, using Docker. Pass/Fail GH pull requests.
JavaScript
2,236
star
14

bubblewrap

Bubblewrap is a Command Line Interface (CLI) that helps developers to create a Project for an Android application that launches an existing Progressive Web App (PWAs) using a Trusted Web Activity.
TypeScript
2,184
star
15

preload-webpack-plugin

Please use https://github.com/vuejs/preload-webpack-plugin instead.
JavaScript
2,162
star
16

worker-plugin

👩‍🏭 Adds native Web Worker bundling support to Webpack.
JavaScript
1,914
star
17

prerender-loader

📰 Painless universal pre-rendering for Webpack.
JavaScript
1,912
star
18

simplehttp2server

A simple HTTP/2 server for development
Go
1,735
star
19

jsvu

JavaScript (engine) Version Updater
JavaScript
1,698
star
20

size-plugin

Track compressed Webpack asset sizes over time.
JavaScript
1,675
star
21

clooney

Clooney is an actor library for the web. Use workers without thinking about workers.
JavaScript
1,419
star
22

browser-fs-access

File System Access API with legacy fallback in the browser
JavaScript
1,316
star
23

proxx

A game of proximity
TypeScript
1,296
star
24

application-shell

Service Worker Application Shell Architecture
JavaScript
1,169
star
25

dark-mode-toggle

A custom element that allows you to easily put a Dark Mode 🌒 toggle or switch on your site:
JavaScript
1,140
star
26

pwacompat

PWACompat to bring Web App Manifest to older browsers
JavaScript
1,130
star
27

container-query-polyfill

A polyfill for CSS Container Queries
TypeScript
1,106
star
28

idlize

Helper classes and methods for implementing the idle-until-urgent pattern
JavaScript
1,048
star
29

houdini-samples

Demos for different Houdini APIs
JavaScript
967
star
30

css-triggers

A reference for the render impact of mutating CSS properties.
JavaScript
893
star
31

jsbi

JSBI is a pure-JavaScript implementation of the official ECMAScript BigInt proposal.
JavaScript
885
star
32

howto-components

Literate code examples for common UI patterns.
JavaScript
851
star
33

tooling.report

tooling.report a quick way to determine the best build tool for your next web project, or if tooling migration is worth it, or how to adopt a tool's best practice into your existing configuration and code base.
JavaScript
841
star
34

page-lifecycle

PageLifecycle.js is a tiny JavaScript library that allows developers to easily observe Page Lifecycle API state changes cross browser
JavaScript
795
star
35

css-paint-polyfill

CSS Custom Paint / Paint Worklet polyfill with special browser optimizations.
JavaScript
709
star
36

estimator.dev

🧮 Calculate the size and performance impact of switching to modern JavaScript syntax.
JavaScript
667
star
37

picture-in-picture-chrome-extension

JavaScript
655
star
38

web-audio-samples

Web Audio API samples by Chrome Web Audio Team
JavaScript
654
star
39

comlink-loader

Webpack loader to offload modules to Worker threads seamlessly using Comlink.
JavaScript
616
star
40

pwa-wp

WordPress feature plugin to bring Progressive Web Apps (PWA) to Core
PHP
603
star
41

ProgressiveWordPress

A Sample WordPress-based Progressive Web App
JavaScript
570
star
42

web-push-codelab

JavaScript
549
star
43

gulliver

A PWA directory, focusing on collecting PWA best practices and examples.
JavaScript
549
star
44

text-app

A text editor for ChromeOS and Chrome
JavaScript
545
star
45

progressive-tooling

A list of community-built, third-party tools that can be used to improve page performance
JavaScript
545
star
46

wasm-feature-detect

A small library to detect which features of WebAssembly are supported.
JavaScript
518
star
47

svgomg-twa

A sample that project Trusted Web Activities technology to wrap SVGOMG in an Android Application
Shell
512
star
48

web-vitals-report

Measure and report on your Web Vitals data in Google Analytics
JavaScript
494
star
49

text-editor

A text editor build on the Native File System APIs
JavaScript
487
star
50

pptraas.com

Puppeteer as a service
JavaScript
455
star
51

chrome-for-testing

JavaScript
442
star
52

progressive-rendering-frameworks-samples

Samples and demos from the Progressive Rendering I/O talk
JavaScript
407
star
53

wasm-bindgen-rayon

An adapter for enabling Rayon-based concurrency on the Web with WebAssembly.
JavaScript
404
star
54

MiniMobileDeviceLab

A mini mobile web device lab
Objective-C
396
star
55

webm-wasm

webm-wasm lets you create webm videos in JavaScript via WebAssembly.
C++
386
star
56

cronet-sample

A sample for the Cronet library
Java
381
star
57

link-to-text-fragment

Browser extension that allows for linking to arbitrary text fragments.
JavaScript
370
star
58

webpack-training-project

A training project for learning Webpack optimizations
JavaScript
368
star
59

pinch-zoom

TypeScript
366
star
60

samesite-examples

Examples of using the SameSite cookie attribute in a variety of language, libraries, and frameworks.
HTML
365
star
61

airhorn

Air horn
JavaScript
361
star
62

buffer-backed-object

Buffer-backed objects in JavaScript.
JavaScript
349
star
63

first-input-delay

A JavaScript library for measuring First Input Delay (FID) in the browser.
JavaScript
347
star
64

AutoWebPerf

AutoWebPerf provides a flexible and scalable framework for running web performance audits with arbitrary audit tools including PageSpeedInsights, WebPageTest and more.
JavaScript
344
star
65

tti-polyfill

Time-to-interactive polyfill
JavaScript
333
star
66

react-shrine

"Shrine" Progressive Web App sample built with React
JavaScript
330
star
67

dynamic-import-polyfill

A fast, tiny polyfill for dynamic import() that works in all module-supporting browsers
JavaScript
320
star
68

wasi-fs-access

This is a demo shell powered by WebAssembly, WASI, Asyncify and File System Access API.
TypeScript
293
star
69

native-url

Node's url module implemented using the built-in URL API.
JavaScript
284
star
70

two-up

TypeScript
270
star
71

adaptive-loading

Demos for Adaptive Loading - differentially deliver fast, lighter experiences for users on slow networks & devices
JavaScript
266
star
72

so-pwa

A progressive web app to read Stack Overflow content.
JavaScript
255
star
73

import-from-worker

It’s like import(), but runs the module in a worker.
JavaScript
246
star
74

sample-pie-shop

Example e-commerce site to explore PWA (Progressive Web App) use cases.
JavaScript
234
star
75

file-drop

A simple file drag and drop custom-element
TypeScript
226
star
76

form-troubleshooter

TypeScript
211
star
77

postcss-jit-props

A CSS custom property helper based on PostCSS. Supply a pool of variables and this plugin will add them to the stylesheet as they are used.
JavaScript
203
star
78

serial-terminal

Demo application for the Web Serial API.
TypeScript
191
star
79

audioworklet-polyfill

🔊 Polyfill AudioWorklet using the legacy ScriptProcessor API.
JavaScript
190
star
80

http2-push-manifest

Generate a list of static resources for http2 push.
JavaScript
187
star
81

pointer-tracker

Track mouse/touch/pointer events for a given element.
TypeScript
183
star
82

pr-bot

🤖 Compare your base branch to a pull request and run plugins over it to view differences
JavaScript
179
star
83

discovery

Discoveries on Sustainable Loading research
177
star
84

imagecapture-polyfill

MediaStream ImageCapture polyfill. Take photos from the browser as easy as .takePhoto().then(processPhoto)
JavaScript
176
star
85

tasklets

JavaScript
176
star
86

credential-management-sample

Credential Management Sample
HTML
157
star
87

wasm-av1

Port of the AV1 Video codec to WebAssembly
C
150
star
88

houdini.how

A community-driven gathering place for CSS Houdini worklets and resources.
JavaScript
150
star
89

perf-track

Tracking framework performance and usage at scale
Svelte
148
star
90

kv-storage-polyfill

A polyfill for the kv-storage built-in module.
JavaScript
145
star
91

telnet-client

TypeScript
142
star
92

wadb

A TypeScript implementation of the Android Debug Bridge(ADB) protocol over WebUSB
TypeScript
142
star
93

pwa-workshop-codelab

JavaScript
142
star
94

http2push-gae

Drop-in HTTP2 push on App Engine
HTML
140
star
95

chromeos_smart_card_connector

Smart Card Connector App for Chrome OS
C++
133
star
96

snapshot

TypeScript
133
star
97

devwebfeed

Firehose of team++ resources
JavaScript
130
star
98

sample-currency-converter

A sample currency conversion Progressive Web App
JavaScript
129
star
99

extension-manifest-converter

Python
119
star
100

json-parse-benchmark

Benchmark comparing JSON.parse vs. equivalent JavaScript literals across JavaScript engines.
JavaScript
119
star