When we think about building modern web applications, we think about being online, about updating users with the latest news and offers. We think about flashy, and attractive animations. In the process of building a web application we often forget that the reality can be dramatically different. We are so busy focusing on the design, content, digital marketing that we forget one of the most important parts of keeping the website running – the users. In reality our users are located in various parts of the globe. They could be offline, or browsing on an old device using often flaky connection, or simply have limited data. How can we make sure that everyone has relatively the same experience using our product, no matter in what conditions they are using it?
If you heard of PWAs, you also heard of service workers, but what are they, how do they work, and what does it have to with anything?
You can think of it as a gatekeeper for your network requests. Service workers have a total control over every request made from a website. Since they run in the background, they don’t need the webpage to be open. That gives them ability to listen and respond to events such as push messages sent from the server to the browser.
Scenario no. 1 – without service worker
You go to a website. Your browser makes a request to the server. The server responds with index.html file. Your browser parses through that file and makes additional requests to the server for the remaining files necessary to render the page. After the page is rendered you can enjoy using the website. You close the tab. What happens when you come back to that page? The process starts all over again. Now imagine that in the middle of that process you lost the connection. Bad news, once the connection is back the process will start all over again. Simply said, every time the connection is lost, the page is gone. If you don’t have access to the server, you simply don’t have access to anything.
Scenario no.2 – with service worker
You go to the website. Your browser makes a request to the server. The server responds with index.html file. Your browser parses through that file and finds service worker. The service worker is being downloaded, installed, and activated. Your browser continues making requests as usual, then renders the page. You use the page, and as usual, leave the page at some point. The process doesn’t seem any different, until you come back to that page. The logic behind service workers kicks in after it was installed and activated. With that being said, service worker won’t work until you return to the page after initial installation. From that point service worker will handle all coming and going requests. In the case that you lost connection, the page can still be loaded from the cache. The true power of service workers is ability to run in the background. This means that even if you leave the page, service worker will make sure that the content gets updated, and when you return, it’s ready to be loaded immediately. Imagine working all day and not using your precious phone. After work you get on the train, the connection is lost immediately and you can’t access any social media. Aha! Here comes a surprise. Your best friend service worker was busy all day preloading all new content for you. Now you can open your favorite page and be updated on what your friends ate for a dinner. How awesome, right?
What about malicious code running in the background of your phone hijacking all requests, you may ask?
With great power comes great responsibility. Using service worker, you can easily hijack connections, modify them, and fabricate responses. To prevent these malicious activities service workers can only be registered over a secure connection. You don’t need to worry about it during development process – localhost is considered to be secured. However, you definitely have to take care of the SSL certificates once your web app goes live. You can opt for paid or unpaid certificate. Should you decide to save some money on that step Let’s Encrypt is a free, automated, and open certificate authority.
Since service worker runs in the background, it has a separate lifecycle from the web page. Let’s have a look at its cycles.
Installation and caching
After the registration of a service worker is completed, it triggers installation process. This is a perfect time to pre-cache all of the resources needed for the shell of our app.
Once the service worker has been installed it’s then activated. The service worker will now control all pages under its scope. You may now ask: what happens when after some time our cache gets outdated? The browser doesn’t know which cache we are going to use, but storing all outdated caches takes a lot of space. The activation process seems to be a great place to solve this issue.
Fetch and updating cache
After the service worker was activated, it can now control events such as fetch, message, and push events which occur whenever network request or message is sent from the page.
In order to save memory, the browser can terminate the service worker. It will be restarted as needed.
When the service worker is registered, it will only handle requests within its scope. What it really means is that your service worker will only have a power over the scope that it is placed in. If you place the service worker in root folder, that service worker will be in control of all other pages. However, if your service worker is placed in a particular folder, it will then have control over that folder only.
What you should also remember is that any page can register a service worker, there are no limits to the amount of service workers, but once they are installed, they will be there forever, or until they are manually unregistered by the user.
There are a handful of useful cache strategies, but choosing the right one always depends on the needs of your application. Let’s have an overview of a few of most common strategies.
Cache first, then network – if the request matches the cache entry, otherwise try to fetch the resource from the network. This is a great strategy for storing commonly used files such as components. However, you should be careful when using this strategy for the application data or resources that change often – you may get old data.
Network first, then cache – using this method you will try to fetch the resources from the network. If the network request is unsuccessful, the browser will try to fetch the resource from the cache. This is a good method to use for the apps containing a lot of fresh data, such as social media platforms, game leaderboards, or articles. This method guarantees that the network request for freshest data will be attempted, however it is unsuccessful, you will still see the old data. The major flaw of this strategy is that when the user has a slow or intermittent connection, they will have to wait either until the data gets loaded from the server, or until the request times out.
Cache only – using this method, your browser will try to resolve the request from the cache. If the resources are unavailable, the request fails. This is a great option for making sure that no network request is made, for example when you turn on save battery mode on your device.
Cache then network – this strategy is ideal for data that updates frequently or when it’s important to get the data on the screen as fast as possible. Using this method, you would make two parallel requests – one to the network and one to the cache, then display the cached version first, and update the UI once network response arrives, unless the network data returns before the cache.
Service Workers are a new technology, currently supported by Chrome, Firefox, Opera, and Samsung Internet (coming up in Edge and Safari). What happens if the browser that you are using doesn’t support them? The browser will simply ignore the part of code responsible for Service Worker and default to its natural behavior. Simply said, you won’t benefit from Service Worker, but it won’t break anything as well.
What are push notifications?
Push notifications are messages that are ‘pushed’ from the server user’s device. They allow us to communicate with the users even if the browser is closed. The ability to reach out to users at any time gives us a is incredibly powerful way to engage with the user, bring them back to the application.
Push notifications consist of two building blocks: the Notifications API, and the Push API. The first one lets us to display the messages to the user, while the latter is responsible for handling the Push Messages received from the server, even when the app is closed. The Notification and Push API’s are built on top of the service worker API, which responds to push message events in the background and links them to the application.
Push messages meant to become notifications are sent from a server directly to the push service, and contain the information necessary for the push service to send it to the right client and wake up the correct service worker. This process happens even when the browser is closed and the service worker is deactivated. When the service worker receives the message, it wakes up to display the message and then deactivates again. However, if the user decides to interact with the notifications service worker wakes up again, this time to handle that user’s interaction.
This API is solely responsible for displaying the notifications to the user. This API uses the same mechanisms as native apps, giving the notifications a completely native look and feel.
Before the notifications can be displayed, the applications need to prompt the user with permission request. The user’s response is the stored along with the app, so when the user comes back, the window does not pop up again with the same request.
Background synchronization is a relatively new but very important feature, as it allows you to synchronize the data even if the user is offline. Let’s imagine, that the user started filling out the form while online. By the time the form was ready to be sent, the user was already offline. How frustrating it really is when at the end of filling out form you can’t save them, all the data is gone and you have to start over when the connection is back? Thankfully using background synchronization allows us to proceed with sending the data to the server. How does it work? You would probably think that we simply send the request, that request is stored in the cache until the connection is back, right? Unfortunately, you can’t cache ‘POST’ requests. So how do we do it then? We need to register sync tasks, in other words we need to tell the service worker that there are some tasks that require to be taken care of when the connection is back. But where is that data that we want to send stored in the meantime? IndexedDB is the best place to store the data requested to be sent. When the connectivity is reestablished the sync event fires, and the service worker executes all tasks it has stored. The beauty of this feature is that user doesn’t have to have the browser open – everything happens in the background!
Service workers possibilities are countless, and this article only touches on a few of them, but be sure that mastering your service worker skills and caching techniques will make your web apps reliable, fast and engaging whether your users are offline, online or using flaky connection.