Add a Service Worker to Your Site

One of the best things to do for your website in 2022 is to add a service rep if you don’t already have one. Service agents add superpowers to your website. Today I want to show you some of the amazing things they can do, and give you a paint-by-numbers boilerplate that you can use right away on your website.

What are service employees?

A service worker is a special type of JavaScript file that behaves like Middleware for your side. Every request that comes from the site and every response it gets back goes through the service worker file first. Service employees also have access to a special cache in which they can store responses and assets locally.

Together, these functions enable you to …

  • Deploy frequently accessed assets from your local cache instead of the network to reduce data usage and improve performance.
  • Provide access to important information (or even your entire website or app) when the visitor goes offline.
  • Pre-fetch critical assets and API responses so they are ready when the user needs them.
  • Deploy fallback assets in response to HTTP errors.

In short, service workers enable you to build faster, more resilient web experiences.

Unlike normal JavaScript files, service workers do not Have access to the DOM. They also run on their own thread, so they don’t block other JavaScripts from running. Service workers are designed to be completely asynchronous.

safety

Because service agents intercept every request and response for your site or app, they are subject to some important security restrictions.

Service employees follow a same-origin policy.

You cannot run your service worker from a CDN or a third party provider. It must be hosted in the same domain that it is running in.

Service staff only work on sites with an installed SSL certificate.

Many web hosts provide SSL certificates for free or for a small fee. If you’re comfortable with the command line, Let’s Encrypt lets you install one for free too.

There is an exception to the SSL certificate requirement for localhost Tests, but you can’t tell your service worker about that file:// Protocol. A local server must be running.

Add a service agent to your site or web app

In order to hire a service employee, the first thing we have to do is to register it with the browser. You can call a service representative through the navigator.serviceWorker.register() Method. Pass in the path to the service worker file as an argument.

navigator.serviceWorker.register('sw.js');

You can do this in an external JavaScript file, but you prefer to do it directly in a script element inline in my HTML to get it executed asap.

Unlike other types of JavaScript files, service workers only work for the directory in which they exist (and all subdirectories). A service worker file under /js/sw.js would only work for files im /js Directory. Therefore, you should put your service worker file in the root directory of your site.

Although service agents have fantastic browser support, it is a good idea to make sure the browser supports them before running your registration script.

if (navigator && navigator.serviceWorker) {
  navigator.serviceWorker.register('sw.js');
}

After the service employee Installed, the browser can activate it. This usually only happens if …

  • there is currently no service employee active, or
  • the user refreshes the page.

The service employee does not carry out any inquiries or intercept them until he activated.

Listen to requests from a service representative

As soon as the service employee is active, it can start intercepting requests and performing other tasks. We can listen to inquiries self.addEventListener() and the fetch Event.

// Listen for request events
self.addEventListener('fetch', function (event) {
  // Do stuff...
});

Within the event listener, the event.request property is the request object itself. To simplify matters, we can use it in the request Variable.

Certain versions of the Chromium browser have a bug that triggers an error when the page is opened in a new tab. Fortunately, there is a simple solution from Paul Irish that I am adding to all of my service staff just in case:

// Listen for request events
self.addEventListener('fetch', function (event) {

  // Get the request
  let request = event.request;

  // Bug fix
  // https://stackoverflow.com/a/49719964
  if (event.request.cache === 'only-if-cached' && event.request.mode !== 'same-origin') return;

});

As soon as your service representative is active, every single request is sent and intercepted with the. fetch Event.

Service worker strategies

Once your service representative is installed and activated, you can intercept requests and responses and process them in various ways. There are two main strategies you can use with your service rep:

  1. Network first. With the network-first approach, you forward requests to the network. If the request is not found or if there is no network connection, look in the service worker cache for the request.
  2. Offline first. With an offline-first approach, you first look for a requested asset in the service worker cache. If it is not found, send the request to the network.

Network first and offline-first Approaches work together. Depending on the type of asset being requested, you will likely use mix-and-match approaches.

Offline-First is great for large assets that don’t change very often: CSS, JavaScript, images, and fonts. Network-First is better for frequently updated assets such as HTML and API requests.

Strategies for caching assets

How do you get assets in your browser’s cache? Typically, you will use two different approaches depending on the nature of the assets.

  1. Pre-cache during installation. Every site and web app has a number of core elements that are used on almost every page: CSS, JavaScript, a logo, favicon, and fonts. You can do this during the install Event and serve them with an offline first approach whenever requested.
  2. Cache as a browser. Your website or app likely has assets that are not accessed on every visit or by every visitor; Things like blog posts and images that go with articles. For these assets, you may want to cache them in real time when the visitor accesses them.

You can then deploy these cached assets either by default or as fallback, depending on your approach.

Implementation of network-first and offline-first strategies for your service employee

In one fetch Event in your service employee who request.headers.get('Accept') Method returns the MIME type for the content. This allows us to determine what type of file the requirement applies to. MDN has a list of common files and their MIME types. For example, HTML files are of the MIME type text/html.

We can put the file type we are looking for into the String.includes() Method as an argument and use if -Instructions to respond in different ways depending on the file type.

// Listen for request events
self.addEventListener('fetch', function (event) {

  // Get the request
  let request = event.request;

  // Bug fix
  // https://stackoverflow.com/a/49719964
  if (event.request.cache === 'only-if-cached' && event.request.mode !== 'same-origin') return;

  // HTML files
  // Network-first
  if (request.headers.get('Accept').includes('text/html')) {
    // Handle HTML files...
    return;
  }

  // CSS & JavaScript
  // Offline-first
  if (request.headers.get('Accept').includes('text/css') || request.headers.get('Accept').includes('text/javascript')) {
    // Handle CSS and JavaScript files...
    return;
  }

  // Images
  // Offline-first
  if (request.headers.get('Accept').includes('image')) {
    // Handle images...
  }

});

Network first

In each if Statement we use the event.respondWith() Method to modify the response sent back to the browser.

For assets that use a network-first approach, we’ll use the fetch() Method, transfer in the request, to run through the request for the HTML file. If it returns successfully, we will return that response in our callback function. This is the same behavior as if you had no service agent at all.

If there is an error we can use Promise.catch() to change the answer instead of showing the standard browser error message. We can use that caches.match() Method to find that page, and return it instead of the network response.

// Send the request to the network first
// If it's not found, look in the cache
event.respondWith(
  fetch(request).then(function (response) {
    return response;
  }).catch(function (error) {
    return caches.match(request).then(function (response) {
      return response;
    });
  })
);

Offline first

For assets using an offline-first approach, we first check the browser cache using the caches.match() Method. If a match is found, we will return it. Otherwise we will use the fetch() Method to the request with in the network.

// Check the cache first
// If it's not found, send the request to the network
event.respondWith(
  caches.match(request).then(function (response) {
    return response || fetch(request).then(function (response) {
      return response;
    });
  })
);

Cache core assets in advance

Inside a install We can do the event listener in the service worker caches.open() Method to open a service worker cache. We pass the name we want to use for the cache, app, as an argument.

The cache is area-specific and limited to your domain. It cannot be accessed by other sites, and if they have a cache of the same name, the contents will be completely segregated.

the caches.open() Method returns a promise. If a cache with this name already exists, the promise will be resolved. If not, the cache is created first and then resolved.

// Listen for the install event
self.addEventListener('install', function (event) {
  event.waitUntil(caches.open('app'));
});

Next we can a. concatenate then() Method to our caches.open() Method with a callback function.

To add files to the cache, we need to request them, which we do with the new Request() Constructor. We can use that cache.add() Method to add the file to the service worker cache. Then we return that cache Object.

We want them install Event to wait for our file to be cached before finalizing it event.waitUntil() Method:

// Listen for the install event
self.addEventListener('install', function (event) {

  // Cache the offline.html page
  event.waitUntil(caches.open('app').then(function (cache) {
    cache.add(new Request('offline.html'));
    return cache;
  }));

});

I find it helpful to create an array with the paths to all of my core files. Then, in the install Event listener, after opening my cache, I can iterate over and add to any element.

let coreAssets = [
  '/css/main.css',
  '/js/main.js',
  '/img/logo.svg',
  '/img/favicon.ico'
];

// On install, cache some stuff
self.addEventListener('install', function (event) {

  // Cache core assets
  event.waitUntil(caches.open('app').then(function (cache) {
    for (let asset of coreAssets) {
      cache.add(new Request(asset));
    }
    return cache;
  }));

});

Caching while surfing

Your website or app likely has assets that are not accessed on every visit or by every visitor; Things like blog posts and images that go with articles. For these assets, you may want to cache them in real time when the visitor accesses them. On subsequent visits, you can load them directly from the cache (with a offline-first Approach) or serve as fallback if the network fails (with a Network first approach).

When a fetch() Method returns a successful one response, we can use that Response.clone() Method to make a copy of it.

Next we can do the caches.open() -Method to open our cache. Then we use that cache.put() Method to cache the copied response by using the request and copy of response as arguments. Since this is an asynchronous function, we’ll put our code in the event.waitUntil() Method. This will prevent the event from ending before we save ours copy to cache. As soon as the copy is saved, we can return that response like normal.

/ Explanation We use cache.put() instead of cache.add() because we already have one response. Using cache.add() would make another network call.

// HTML files
// Network-first
if (request.headers.get('Accept').includes('text/html')) {
  event.respondWith(
    fetch(request).then(function (response) {

      // Create a copy of the response and save it to the cache
      let copy = response.clone();
      event.waitUntil(caches.open('app').then(function (cache) {
        return cache.put(request, copy);
      }));

      // Return the response
      return response;

  }).catch(function (error) {
      return caches.match(request).then(function (response) {
        return response;
      });
    })
  );
}

Put everything together

I have put together a copy-paste boilerplate for you on GitHub. Add your core resources coreAssets array and register it on your site to get started.

If you don’t do anything else, it will give your website a huge boost in 2022.

But there is so much more you can do with service people. There are advanced caching strategies for APIs. You can provide an offline page with critical information if a visitor loses their network connection. You can clean up bloated caches while the user is browsing.

Jeremy Keith’s book, To go offline, is a great introduction for service staff. If you’re looking to take things to the next level and dig into progressive web apps, Jason Grigsby’s book delves into the various strategies you can use.

And for a pragmatic deep dive that you can do in about an hour, I also have a course and e-book on service people with lots of code samples and a project to work on.

Leave a Comment

Your email address will not be published.