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:
-
Serve the service worker through a rule.
-
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';45skipWaiting();6clientsClaim();7precacheAndRoute(self.__WB_MANIFEST || []);89new 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';23export default new Router()4 // cache the service worker at the edge for 1 day5 .match('/service-worker.js', {6 caching: {7 max_age: '1d',8 bypass_client_cache: true,9 },10 })1112 // serve the service worker from the /dist directory13 .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.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';23/*4 Your client-side code here5*/67// install the service worker8document.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';23export default new Router()4 // cache the service worker at the edge for 1 day5 .match('/service-worker.js', {6 caching: {7 max_age: '1d',8 bypass_client_cache: true,9 },10 })1112 // serve the service worker from the /dist directory13 .match('/service-worker.js', ({serviceWorker}) => {14 serviceWorker('dist/service-worker.js');15 })1617 // cache policy for product API calls18 .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:
- You have built, served, and registered the service worker.
- The link is displayed in the viewport (i.e., the area of the web page that is currently visible to the user).
- The link matches at least one rule that contains both of the following features:
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'23function 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}1415export 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'23export default new Router()4 // This rule's path matches the links href attribute5 .match("/pages/:id", {6 caching: {7 max_age: "1h", // Caches the response on the edge for 1 hour8 service_worker_max_age: "1h" // Enables automatic prefetching and caches the response in the browser SW cache for 1 hour9 }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';23/*4 Your client-side code here5*/67document.addEventListener('DOMContentLoaded', () => {8 install({9 /* install options */10 });1112 // prefetch URLs when the user hovers over them13 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';23/*4 Your client-side code here5*/67// install the service worker8document.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';23function 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';56export default function ProductListing({products}) {7 const {locale} = useRouter(); // you can omit this if you're not using localization89 return (10 <ul>11 {products.map((product, i) => (12 <li key={i}>13 <Link href={product.url} passHref>14 <Prefetch15 url={createNextDataURL({16 href: product.url,17 locale, // you can omit this if you're not using localization18 routeParams: {19 // keys must match the param names in your next page routes20 // 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}3435export async function getServerSideProps({params: {id}}) {36 const products = await fetch(/* fetch from your api */).then((res) =>37 res.json()38 );3940 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>67<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';34new 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';34new 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 page10 attribute: 'src', // the attribute holding the URL to prefetching11 as: 'image', // the type of asset being prefetched12 },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';56new 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});1819function deepFetchResponsiveImages({$el, el, $}: DeepFetchCallbackParam) {20 const urlTemplate = $el.attr('data-src');21 const dataWidths = $el.attr('data-widths');2223 if (dataWidths && urlTemplate) {24 const widths = JSON.parse(dataWidths);2526 for (let width of widths.slice(0, 2)) {27 const url = urlTemplate?.replace(/\{width\}/, width);28 prefetch(url, 'image');29 }30 }31}