Edgio

CDN-as-Code Predictive Prefetching Setup

If your website uses Edgio Sites or is based on a JavaScript front-end framework, then you can install the @edgio/prefetch package directly, take advantage of additional package features, and achieve deeper integration with your site.
Key information:
  • By default, only content that has already been cached on the POP closest to the user may be prefetched. Edgio returns a 412 Precondition Failed response for prefetch requests that result in cache misses. This ensures that your infrastructure does not experience additional load due to prefetching.
  • Due to security requirements, prefetching requires the HTTPS protocol. An exception to this requirement occurs when using localhost.

Setup

Perform the following steps:
  1. Build the service worker with the Prefetcher class.
  2. Serve the service worker through a rule.
  3. Register the service worker.
  4. Enable prefetching for the desired requests by adding the following features within one or more rules:
    Alternatively, you may manually enable prefetching for specific requests.

Building the Service Worker

Integrating prefetching into your site requires building the service worker with the Prefetcher class.
One method for adding a service worker to your site is to use Google’s Workbox.
Sample Service Worker:
The following sample service worker is based on Workbox using the Prefetcher class from @edgio/prefetch:
JavaScriptservice-worker.js
1import {skipWaiting, clientsClaim} from 'workbox-core';
2import {precacheAndRoute} from 'workbox-precaching';
3import {Prefetcher} from '@edgio/prefetch/sw';
4
5skipWaiting();
6clientsClaim();
7precacheAndRoute(self.__WB_MANIFEST || []);
8
9new Prefetcher().route();

Serving the Service Worker

After you have created a service worker, your router needs to be configured to serve the file. If you are using one of our connectors, then it should be automatically configured for you.
Example:
The following example defines a route that serves requests for /service-worker.js. However, your code will vary according to the location of your service worker file.
JavaScriptroutes.js
1import {Router} from '@edgio/core';
2
3export default new Router()
4 // cache the service worker at the edge for 1 day
5 .match('/service-worker.js', {
6 caching: {
7 max_age: '1d',
8 bypass_client_cache: true,
9 },
10 })
11
12 // serve the service worker from the /dist directory
13 .match('/service-worker.js', ({serviceWorker}) => {
14 serviceWorker('dist/service-worker.js');
15 });

Registering the Service Worker

After you have created and served the service worker, it needs to be registered. If you are using a front-end framework that does not automatically register service workers, then you should invoke the install function from the @edgio/prefetch to install the service worker within your client-side code. See InstallOptions for additional options when installing the service worker.
Example:
Assuming that your client-side code is in app.js, you can install the service worker by adding the following code to app.js:
JavaScriptapp.js
1import { install , prefetch } from '@edgio/prefetch/window';
2
3/*
4 Your client-side code here
5*/
6
7// install the service worker
8document.addEventListener('DOMContentLoaded', () => {
9 install({
10 /* install options */
11 });
12});
Now when your client-side code runs, the service worker will be installed and ready to prefetch URLs.

Defining a Prefetching Caching Policy

Automatic prefetching requires an edge and service worker caching policy. From within your router, define a route that caches responses at the edge and in the service worker. Optionally, set a longer cache time for greater performance.
Example:
The following example defines a route that caches product API calls for one hour:
JavaScriptroutes.js
1import {Router} from '@edgio/core';
2
3export default new Router()
4 // cache the service worker at the edge for 1 day
5 .match('/service-worker.js', {
6 caching: {
7 max_age: '1d',
8 bypass_client_cache: true,
9 },
10 })
11
12 // serve the service worker from the /dist directory
13 .match('/service-worker.js', ({serviceWorker}) => {
14 serviceWorker('dist/service-worker.js');
15 })
16
17 // cache policy for product API calls
18 .get('/api/products/:id.json', {
19 caching: {
20 max_age: '1h',
21 stale_while_revalidate: '1d',
22 service_worker_max_age: '1h',
23 }
24 });

Default Caching Policy for Manually Prefetching

If the caching.service_worker_max_age feature has not been defined, you may still manually prefetch content. By default, manually prefetched content will be cached by the service worker for 2 minutes. Change the default time to live (TTL) by setting defaultMaxAgeSeconds when initializing the Prefetcher instance in your service worker.
Example:
Set a default TTL of 10 minutes by initializing the Prefetcher instance as shown below:
JavaScriptservice-worker.js
1const prefetcher = new Prefetcher({defaultMaxAgeSeconds: 60 * 10}); // set the local cache TTL to 10 minutes

Automatic Prefetching

Edgio will attempt to prefetch links that meet all of the following conditions:
Prefetch requests are given the lowest priority. This ensures that they do not block more critical requests like API calls, images, scripts, and navigation.
By default, the response varies according to whether the requested content has been cached within the POP closest to the user.
  • If a cached response is found, then Edgio will serve this cached content to the browser. The browser will then cache it locally for the duration defined by the Set Service Worker Max Age (service_worker_max_age) feature.
  • If a cached response is not found, then Edgio will return a 412 Precondition Failed response.
    Override this behavior by enabling the data-include-cache-misses attribute.
Next.js Framework Example:
This example generates an HTML page with a list of links:
JavaScriptapp/pages/page.jsx
1import Link from 'next/link'
2
3function Pages({ pages }) {
4 return (
5 <ul>
6 {pages.map((post) => (
7 <li key={page.id}>
8 <Link href={`/pages/${page.id}`}>{page.title}</Link>
9 </li>
10 ))}
11 </ul>
12 )
13}
14
15export default Pages
Add the following rule to automatically cache and prefetch all navigation links in the above example:
JavaScriptroutes.js
1import { Router } from '@edgio/core/router'
2
3export default new Router()
4 // This rule's path matches the links href attribute
5 .match("/pages/:id", {
6 caching: {
7 max_age: "1h", // Caches the response on the edge for 1 hour
8 service_worker_max_age: "1h" // Enables automatic prefetching and caches the response in the browser SW cache for 1 hour
9 }
10 })

Manual Prefetching

Manual prefetching also uses prefetch function, but it is manually called from your client-side code. This is especially useful when prefetching based on user interactions or other events. The prefetch function accepts a URL and an optional config object with properties defined in the PrefetchConfiguration interface. This function may be called at any time after the service worker is installed.
The following sections describe various ways to implement manual prefetching using the prefetch function.

Prefetching on Hover

This example shows how you can prefetch links with an href attribute when the user hovers over them:
JavaScriptapp.js
1import { install, prefetch } from '@edgio/prefetch/window';
2
3/*
4 Your client-side code here
5*/
6
7document.addEventListener('DOMContentLoaded', () => {
8 install({
9 /* install options */
10 });
11
12 // prefetch URLs when the user hovers over them
13 document.addEventListener('mouseover', (e) => {
14 if (e.target.tagName === 'A') {
15 prefetch(e.target.getAttribute('href'));
16 }
17 });
18});

Prefetching Based on Element Visibility

To prefetch URLs based on element visibility, you can use the watch option when installing the service worker. The watch option accepts an array of objects with selector and callback properties. The selector property is a CSS selector that matches elements to watch for visibility. The callback property is a function that is called when an element matching the selector becomes visible. This element is passed to the callback function as an argument.
The following example will prefetch all links with an href attribute that are visible on the page:
JavaScriptapp.js
1import { install, prefetch } from '@edgio/prefetch/window';
2
3/*
4 Your client-side code here
5*/
6
7// install the service worker
8document.addEventListener('DOMContentLoaded', () => {
9 install({
10 watch: [
11 {
12 selector: 'a[href^="/"]',
13 callback: (el) => {
14 prefetch(el.getAttribute('href'));
15 },
16 },
17 {
18 selector: 'link[href^="/"]',
19 callback: (el) => {
20 prefetch(el.getAttribute('href'));
21 },
22 },
23 ],
24 });
25});

Framework Prefetch Components

Edgio provides prefetch component integration for the following front-end frameworks:
These components allow for easier prefetch integration with your existing framework code without having to call the prefetch function directly.

React

The @edgio/react package provides a Prefetch component that you can wrap around any link to prefetch the link when it becomes visible in the viewport:
JavaScript
1import {Prefetch} from '@edgio/react';
2
3function ProductLink({product}) {
4 return (
5 <Prefetch url={`/api/products/${product.id}.json`}>
6 <a href={`/products/${product.id}`}>{product.name}</a>
7 </Prefetch>
8 );
9}
By default, Prefetch will fetch and cache the URL in the link’s href attribute. If you have a single page app, you should typically prefetch the corresponding API call for the page rather than the page’s HTML. The above example shows how to set the url property to control which URL is prefetched.

Next.js

If you are using Next.js with getServerSideProps, use createNextDataURL from @edgio/next/client to prefetch the data for the linked page.
JavaScript
1import {Prefetch} from '@edgio/react';
2import Link from 'next/link';
3import {useRouter} from 'next/router';
4import {createNextDataURL} from '@edgio/next/client';
5
6export default function ProductListing({products}) {
7 const {locale} = useRouter(); // you can omit this if you're not using localization
8
9 return (
10 <ul>
11 {products.map((product, i) => (
12 <li key={i}>
13 <Link href={product.url} passHref>
14 <Prefetch
15 url={createNextDataURL({
16 href: product.url,
17 locale, // you can omit this if you're not using localization
18 routeParams: {
19 // keys must match the param names in your next page routes
20 // So for example if your product page is /products/[id].js:
21 id: product.id,
22 },
23 })}>
24 <a>
25 <img src={product.thumbnail} />
26 </a>
27 </Prefetch>
28 </Link>
29 </li>
30 ))}
31 </ul>
32 );
33}
34
35export async function getServerSideProps({params: {id}}) {
36 const products = await fetch(/* fetch from your api */).then((res) =>
37 res.json()
38 );
39
40 return {
41 props: {
42 products,
43 },
44 };
45}

Vue.js

The @edgio/vue package provides a Prefetch component that you can wrap around any link to prefetch the link when it becomes visible in the viewport:
JSX
1<template>
2 <Prefetch v-bind:url="/api/for/some/page">
3 <router-link v-bind:to="/some/page">Some page</router-link>
4 </Prefetch>
5</template>
6
7<script>
8 import Prefetch from '@edgio/vue/Prefetch'
9 export default {
10 components: {
11 Prefetch,
12 },
13 }
14</script>
By default, the Prefetch component will fetch and cache the URL in the link’s to attribute (for both router-link and nuxt-link). If you have a single page app, you should typically prefetch an API call for the page rather than the page’s HTML. The above example shows how to set the url property to control which URL is prefetched.

Deep Fetching

By default, prefetching only fetches the JSON API data or HTML document for a prefetched page. In order to achieve truly instant page transitions, all of the page’s assets above the fold need to be prefetched as well. This typically includes images, CSS, and JavaScript. This is where deep fetching comes in. Deep fetching parses the prefetched page and then fetches important assets from the prefetched page.
To add deep fetching to your project, add the DeepFetchPlugin to your service worker. The DeepFetchPlugin is then configured with an array of selectors that describe which assets need to be prefetched:
JavaScriptservice-worker.js
1import {Prefetcher} from '@edgio/prefetch/sw';
2import DeepFetchPlugin from '@edgio/prefetch/sw/DeepFetchPlugin';
3
4new Prefetcher({
5 plugins: [
6 new DeepFetchPlugin([
7 {
8 /* Deep fetching configuration objects go here */
9 },
10 ]),
11 ],
12});
The DeepFetchPlugin can parse both HTML and JSON documents to extract the page assets that must be deep fetched. For Edgio projects that are headless (i.e., the front-end communicates with the backend through an API), you should typically use the JSON option. However, if the backend and front-end endpoints are communicating using HTML responses, then you should use the HTML option. You can mix both HTML and JSON configuration objects in the array passed to the DeepFetchPlugin.

Deep Fetching Urls in JSON Responses

For JSON responses, you should pass the DeepFetchPlugin, which is an array of DeepFetchJsonConfig interface objects. These DeepFetchJsonConfig objects describe the asset URLs in the JSON response that should be prefetched. For example, the snippet below finds product images to deep fetch for a category page response:
JavaScriptservice-worker.js
1new DeepFetchPlugin([
2 // parses the category API response to deep fetch the product images:
3 {
4 jsonQuery: 'Bundles.[**].Products:products(Product).MediumImageFile',
5 jsonQueryOptions: {
6 locals: {
7 // filters out null products:
8 products: (input) => input.filter((prod) => prod),
9 },
10 },
11 maxMatches: 10,
12 as: 'image',
13 },
14]);
The jsonQuery syntax is provided by the json-query library. You can test your JSON queries using their JSON-query Tester Sandbox.

Deep Fetching for HTML Documents

To deep fetch HTML documents, pass the plugin objects that match the DeepFetchHtmlConfig interface and describe which HTML elements need to be prefetched via CSS selectors.
For example, imagine you’re configuring prefetching for a product page and you want to ensure the main product image is prefetched so that it appears immediately when the page loads. If the main product image is displayed with an HTML img element with a CSS class called product-featured-media, it can be prefetched by adding the following to the DeepFetchPlugin:
JavaScriptservice-worker.js
1import {Prefetcher} from '@edgio/prefetch/sw';
2import DeepFetchPlugin from '@edgio/prefetch/sw/DeepFetchPlugin';
3
4new Prefetcher({
5 plugins: [
6 new DeepFetchPlugin([
7 {
8 selector: 'img.product-featured-media', // CSS selector syntax - just like you would use with document.querySelector()
9 maxMatches: 1, // limits the number of matched elements to prefetch to 1 per page
10 attribute: 'src', // the attribute holding the URL to prefetching
11 as: 'image', // the type of asset being prefetched
12 },
13 ]),
14 ],
15});

Computing the URL to Be Prefetched

In the example above the img element’s src attribute contains URL that needs to be prefetched. Sometimes finding the URL to prefetch is not so straightforward. For example, apps sometimes use JavaScript to compute the URL for responsive images based on the user’s device size. In such cases you can provide a callback function which will be passed all matching elements and decide what URLs to prefetch. Here is an example:
TypeScriptservice-worker.js
1import {Prefetcher, prefetch} from '@edgio/prefetch/sw';
2import DeepFetchPlugin, {
3 DeepFetchCallbackParam,
4} from '@edgio/prefetch/sw/DeepFetchPlugin';
5
6new Prefetcher({
7 plugins: [
8 new DeepFetchPlugin([
9 {
10 selector: 'img.grid-view-item__image',
11 maxMatches: 4,
12 as: 'image',
13 callback: deepFetchResponsiveImages,
14 },
15 ]),
16 ],
17});
18
19function deepFetchResponsiveImages({$el, el, $}: DeepFetchCallbackParam) {
20 const urlTemplate = $el.attr('data-src');
21 const dataWidths = $el.attr('data-widths');
22
23 if (dataWidths && urlTemplate) {
24 const widths = JSON.parse(dataWidths);
25
26 for (let width of widths.slice(0, 2)) {
27 const url = urlTemplate?.replace(/\{width\}/, width);
28 prefetch(url, 'image');
29 }
30 }
31}