Learn to Code via Tutorials on Repl.it!

← Back to all posts
How to make your website work offline with Service Workers!
h
Baconman321 (606)

Have

you ever been offline and thought "Uggh, I wish I could play a game online or at least browse one or two websites"?
Well, if you make websites, you can actually make them work offline! I have actually created a few websites of my own that work offline. Check them out if you want! https://portfolio.baconman321.repl.co, https://stargaze.baconman321.repl.co

In this tutorial, you will learn how to use a service worker in accomplishing the task of making your website work offline. I will be making my own repl and showing you the steps to making it work offline. I am assuming you have basic knowledge of JavaScript and web development. Now, ONWARD!

Step 1: Making the website!

Ok, first things first, we have to make a website to make it work offline (obviously, heh).

I have created a repl called "Our test website", and gave it a basic "hello world" structure. When we run it, we see that it gives us "Hello world".

Now, let's actually go to that website and open devtools. From there, navigate to the network tab. You should see a list of options, including something called "network throttling". This is where we can simulate different internet speeds, as well as an offline mode.

Let's click that offline mode, and reload the page. What do you think happens? Well, since we are simulating offline, the web browser gives us the default offline page because we haven't configured our webpage to work offline yet.

Well, that's not fun, is it?

Ok, now for the fun part. Setting up the service worker!

Step 2: Making a service worker!

Now this is the part where I explain what a service worker is, and how to set it up. If you want a more in-depth explanation about what a service worker is (and how it's different compared to a web worker), I have made a short tutorial on the difference between service workers and web workers (which also explains what they are as well). In essence though a service worker is like a proxy for a webpage that sits on the user's computer and is most often used to listen for web requests on that webpage (like getting external files to load for the web page). A cool thing that a service worker can do (which also happens to be something it is used for a lot), is cache files and request them to be loaded instead of the resource from the internet depending on how you configure it.
Here's an diagram of one of the service worker's many purposes. As you can see here, it is in fact like a proxy... acting as an intermediary between the user and the internet.

Ok, you might be thinking "that's nice and all, but when will I make the service worker dude?!!!".

Well, you're in luck because that's what that tutorial is all about!

Hmmm, I don't know why I'd make a tutorial about something that's not related to the main title of the tutorial, but whatever it's probably nothing worth noting... continue on....

Ok, first things first. How do I make a service worker file and where do I place it?
A service worker file is literally a JavaScript file in itself, but when set up as a service worker it gets different properties than a normal JavaScript file. For one, it is on a different thread than the main (wut...?!) thread, meaning it doesn't have access to the DOM (double wut...?!).
Instead of using the window object, it uses a ServiceWorkerGlobalScope, which is most often referred to by the keyword self (which most interestingly enough can also be used to refer to the window object in a normal JS file).

Also, another thing to note: a service worker can only access the files of the folder it is put in, so make sure you put it either in the root, or the folder that contains all the files you want to have control over.

Now, with that in mind, let's make a service worker.

First, let's create an empty .js file and put it in the respective folder containing the contents you want to control. It doesn't matter what you name it, but most websites name it service-worker.js or sw.js. For the sake of simplicity, I'm going to name ours sw.js.

That's nice and all, but it won't magically make things work. As you can see by the image below, it still doesn't work offline.

Now, you might be steaming right now because you came to make your website work offline, not to see me utterly fail. Let me calm you down by saying that no... I did not utterly fail.

I just was showing you what not to do...

You have to register the service worker.
"How do I do that you jerk?!", you might say.
You have to register it from the main thread. A.K.A, you have to register it with normal (normal? A service worker is still JavaScript LOL) JavaScript. For simplicity (because then you'll need to cache that as well... more on that later), instead of creating a seperate JavaScript file, we'll just put it as an inline script in our index.html file.

if("serviceWorker" in navigator){
  
 navigator.serviceWorker.register("sw.js").then(function(registration){
      console.log("Service worker registered successfully!");
}).catch(function(error){
      console.log("Service worker failed to be registered with error: ",error);
   });
}

What we are basically doing here is checking if we can install a service worker. If we can, then register it. If it is rejected, log the error. If not, log that it was registered successfully.
Ah, yes. We have successfully written the script to register the service worker!

If you look in the sources tab of the devtools, you will see that the service worker is registered (as shown by the image below).

That's nice and all, but it still won't work offline.

"WHEN WILL YOU MAKE THIS WORK OFFLINE YOU BLOCKHEAD!?!?"
Woah, there! I'm getting to that soon.
"You better do it right NOW!"
Give me a reason why I should rush to the part!

Those are good reasons...
Ok, well now we are moving on to the part to actually making the service worker "work" (no pun intended... ok maybe it was intended a little).
We have to make an install listener, to listen for when the service worker is installed. After that, cache the files we want to cache.
Put this in your service worker file:

self.addEventListener("install",function(event){
   event.waitUntil(
      caches.open("our-cache").then(function(cache){
         cache.addAll(["/"]);
      })
   );
});

As you can see, we are opening a cache area that stores the cached files we want to cache, then caching them.

Quick note: the addAll() method takes an array of the files you want to cache.

As you notice, we don't cache sw.js.
We don't need to, since it will stay a service worker until either terminated (by the script on the client-side or itself) or until the site data is cleared.
You can name "our-cache" to whatever else, but remember to use the same name when you are fetching the cache.
Ok, now we got that, but we still need to make it work offline.
Here's the plan:
The service worker fetches the website's files when it is offline. If the fetch fails (most likely it's offline), then fetch the cached version of the file.
What we have to do is set up an event listener to listen for a fetch request (when the webpage is requesting files, like if the user navigates to the website, the website tries to fetch another resource or the website is trying to load a certain file).
Under the install event listener, we will create a new event listener:

self.addEventListener("fetch",function(event){
   //from now on we will be working in here
});

Well, that was easy right?
"OFFLINE, HERE I COME!"
Woah, not quite.
"You..."
Ok,ok... moving on!
Now, we have to fetch the data, then return the response. This is the actual "proxy" part. We wait for a request, fetch it ourselves, then send the response based off of what we want to do.
Inside our fetch listener:

event.respondWith(
   fetch(event.request).then(function(response){
      return response;
   }).catch(function(error){
      //We don't really need the error parameter, but if you want to use it you know how to now.
      return caches.match(event.request).then(function (cacheRes) {
       return cacheRes;
      })
   })
);

What we are doing, is fetching the request and sending the response. If the fetch fails, we revert to the cache to send the file. caches.match() searches for the file in cache and returns the response if it is indeed there. If you want to, you can use an if...then statement to see if the cache is there or not. If it's not there then return a response to the user stating that something went wrong.
Otherwise, that's pretty much it!
Let's see the finished sw.js file.

Ahh, yes. Now, for the test.
Let's run it offline and see if it works!

"YES, IT WORKS!!! YESYESYESYES!!! Thank you baconman so much.... wait. What do I do with this?!?!"
Make your website work offline, obviously.
In addition, you can also use service workers for tons of things! Since it can run before the webpage loads, or a little bit after it is exited out of/reloaded, you can use it for cleanup/pre-load work.

One example of a useful thing service workers can do is make an error-reporter. I made a client-side script that listens for errors, then when the webpage is about to be refreshed/exited out of I send the data to the service worker to fetch the server-side and store the data in a .log file. Of course, this is just one example among hundreds of things you can do.

Step 3: What's next?

Well, once you make a website you now have the power to make it work offline! This is often unappealing to bigger websites that have thousands upon thousands of web pages... but for a small gaming site or info website it should be very useful.
Well, what are you waiting for? Go out there and make a website!

And make it work offline...

Challenge:

Try making a version checker that checks if the version of the cache is up to date.

Stuck?

Try looking at https://sortacraft-1.tussiez.repl.co/sw.js in the checkVer() function to give you some hints. I'm not revealing the answer, that's for you to find out ;)


"GIVE ME ANSWERS YOU BLOCKHEAD!!!!"

...NO

Well,

hope you enjoyed this tutorial. Took a little bit to write, so I hope this gets lots of views and such.
Upvote if you like. Comments are always appreciated!
HAHAHAHABYEEEE DON'T HURT ME THANKYOUVERYMUCH!!!!
"Uhm... wut"
HAHAVERYFUNNYYOUKNOWWHATIMTALKINGABOUTBACONBOIOUT!

Commentshotnewtop
xxpertHacker (627)

Hmm... swore you could do the same with HTTP caching?

Or did that require manifest files?

xxpertHacker (627)

@Baconman321 Manifests are, but HTTP caching isn't.

Baconman321 (606)

@xxpertHacker Still I find service workers easier to work with because you can serve static caches and the rest only will be served if offline or a network error occurred.

xxpertHacker (627)

@Baconman321 Manifests were declarative, service workers are imperative; can't say I like service workers better.

Have you looked into server worker life cycles either?

Also, I dislike the unnecessary execution of JavaScript (why I prefer Wasm & native code).

Baconman321 (606)

@xxpertHacker JavaScript does slow down websites a whole lot. Still WASM isn't all that easy if you don't know lower level languages (because it's an assembly language). I guess right now it's good for calculations and all because it's really fast, but yeah I did hear they were going to go further with it.

xxpertHacker (627)

@Baconman321

JavaScript does slow down websites a whole lot.

Yes, that's one of the most important parts!

With a manifest, the browser could simply, and directly, fetch everything in the list in parallel and cache them all directly.

Baconman321 (606)

@xxpertHacker I might as well have to learn http caching and make a tutorial on it too. Although I find that service workers still have a use. If http caching is so great then why don't I see more websites using it (Ok, if http cache is the same as application cache which I highly doubt it, then google is using it...)?

xxpertHacker (627)

@Baconman321

If http caching is so great then why don't I see more websites using it (Ok, if http cache is the same as application cache which I highly doubt it, then google is using it...)?

XD Google does use it! In fact, Repl.it is using it right now.

https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching

But no, application cache =/= HTTP cache.

EpicGamer007 (1291)

wait, so will we be able to use localStorage if we use a service worker?

xxpertHacker (627)

@EpicGamer007 iirc, no, localStorage cannot be used from a worker.

But IndexedDB can be.

Baconman321 (606)

@xxpertHacker Ye. @EpicGamer007 you don't have access to the window object, and technically it's window.localStorage or window.sessionStorage. IndexedDb is allowed, like xxperthacker says. I actually have used indexedDB - it's quite complicated at first (but that just means it offers more control...usually).

xxpertHacker (627)

@Baconman321 IndexedDB doesn't offer any more "control," it's async and allows storing types other than string.

In fact, the original localStorage specification said that other types were supposed to have been allowed, but no browser implemented the spec.

Baconman321 (606)

@xxpertHacker Well then I guess it does offer more control then. Plus, local Storage is synchronous, so I guess it could offer some more benefits (and bring some more disadvantages).

xxpertHacker (627)

@Baconman321 It's not more control per se, that's like saying Arrays offer more control over TypedArrays, since they can hold anything, but TypedArrays can't.

It's just what they can hold ¯\_(ツ)_

Baconman321 (606)

@tussiez LOL I wish more people would see my tutorials... :(

tussiez (602)

@Baconman321 I do too.Good tutorials will gain popularity in time :)