A Service Worker is a relatively new API that’s been introduced in modern web browsers in the last few years. It’s a really important technology. It’s a special kind of web worker that can be installed in your browser to provide special features that were previously unavailable to ordinary web pages like allowing for offline access of website content.
Service Workers are at the core of Progressive Web Apps because they allow caching of resources and push notifications, two of the main distinguishing features that up to now set native apps apart.
The features that go under the Progressive Web App umbrella are mostly oriented towards having the ability to build a fully featured mobile app using web technologies that is not a sub-par experience compared to a native stack.
A Service Worker runs on a separate thread. This means you can’t access many objects available in the main JavaScript thread, including access to the DOM and several APIs like XHR, or Cookies. On the plus side, any Service Worker operation can’t ever block the UI thread, because it’s completely separate.
Keep in mind that service workers need to be served over HTTPS, otherwise, the browser won’t register them.
The end goal of Progressive Web Apps (PWAs) is to be excellent on mobile, and one thing that the web always failed at on mobile is handling the offline state.
Service workers can intercept network requests, add them to the browser cache, using the Cache API, and they can serve cached resources if they detect that the browser is offline and the network is unavailable.
Another big feature is enabling push notifications, through the use of the Push API and the Notifications API, two separate web platform APIs that developers can use in service workers.
Service workers need to be installed in the browser before they can be used.
First, you need to check if the browser implements service workers.
The best way is to check for the existence of serviceWorker
on the navigator object:
if (!('serviceWorker' in navigator)) {
// service workers not supported 😣
return
}
If service workers are available, you can register your service worker by specifying the file where it can be found. You always have a separate file, accessible by the browser. For example, you can have it in a worker.js
file placed in the root directory.
You wait until the page is loaded, then you register the service worker with the navigator.serviceWorker.register()
method:
window.addEventListener('load', () => {
if (!('serviceWorker' in navigator)) {
// service workers not supported 😣
return
}
navigator.serviceWorker.register('/worker.js').then(
() => {
// registered! 👍🏼
},
err => {
console.error('SW registration failed! 😱', err)
}
)
})
I mentioned that a service worker needs to be installed in the browser before it can be used.
When a user first comes to your website, the only thing a service worker can do is be installed.
Anything that’s inside the service worker, any of the functionalities it comes with, are not enabled until the user visits a second page on your site, or refreshes the current page. This is by design.
We just saw how to install a service worker that lives in a worker.js
file, but we didn’t look into that file yet.
In a service worker you can listen for several events emitted by the browser:
fetch
is sent whenever a page of your site requires a network resource. It can be a new page, a JSON API, an image, a CSS file, whatever.install
is sent when the service worker is being installed.activate
is sent when the service worker has been registered and installed. This place is where you can clean up anything related to the older version of the service worker if it’s been updated.sync
is sent if the browser previously detected that the connection was unavailable, and now signals the service worker that the internet connection is working.push
is invoked by the Push API when a new push event is received.When the service worker is installed we can tell the browser to cache specific resources that we would need to serve the page while offline later on:
self.addEventListener('install', event => {
event.waitUntil(
caches
.open('my-site-name')
.then(cache =>
cache.addAll([
'favicon.ico',
'style.css',
'script.js',
'https://fonts.googleapis.com/css?family=Inconsolata:400,700'
])
)
)
})
This piece of code uses the Cache API to make the browser cache all those resources in the cache named my-site-name
.
Let’s see how to listen for a fetch
event to provide the user a cached resource the next time a page from our site is accessed:
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(response => {
if (response) {
//we found an entry in the cache!
return response
}
return fetch(event.request)
})
)
})
We check if the cache contains the resource identified by the request
property, and if not we launch a fetch request to get it.
Notice also the use of self
in the above examples. Workers get a global self
read-only property to allow access to themselves.
Once a service worker is installed, it will continue to run until it’s either removed by the user, or you update it.
To update a service worker, you just need to push a new version of it in the server (even changing one byte will suffice) and the browser will detect it’s a new version, it will download and install it.
Just like when first installed, the new service worker will not be available until the next page load.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!