Exploring Service Workers

Logged Out Routing Walkthrough

Kyle Simpson

Kyle Simpson

You Don't Know JS
Exploring Service Workers

Check out a free preview of the full Exploring Service Workers course

The "Logged Out Routing Walkthrough" Lesson is part of the full, Exploring Service Workers course featured in this preview video. Here's what you'd learn in this lesson:

Kyle walks through additional logic for the router that handles specific cases, dealing with the routing behavior that is not sensitive to whether the user is logged in.


Transcript from the "Logged Out Routing Walkthrough" Lesson

>> Kyle Simpson: All right, so now you're getting a sense of how we can implement our own routing logic using these different strategies. We could have, for example, tried the cache first and then done the fetch. We could have wrapped this in an if (isOnline) and only done the fetch if we were actually online.

And so to jump a little bit forward in history, I'm going to wave a magic wand and pull in from the solution, one of the interim solutions. I'm gonna pull in a much more complete router and then we're gonna talk about each of the different pieces, what decisions I made, instead of having to type all of that out.

Don't worry if you want to type all this stuff yourself, because this logic is included in your final solution file, I've just pulled out an interim step of it.
>> Kyle Simpson: All right, so now there's a bunch more in our router and we're gonna walk through what it's doing.

We're still checking to see if we're handling only our own requests, but now we need to do some handling, specific special kind of handling for any API calls that happen. In particular, I only wanna do a call, attempt a fetch if I'm actually online. So here I have an if (isOnline).

I've set up some fetchOptions here. And you'll notice that instead of credentials: "emit", I'm gonna have credentials: "same-origin". And that's because at least one of my API calls, that's the api/add-post, that does include credentials, it includes your session cookie in it. So we need to make sure to send along any cookies for any API calls.

Even though some of them have it and some of them don't, we've gotta make sure to send those along. We obviously wanna do cache: "no-store" so that those requests don't get cached. Now I’ve made a deliberate choice, this maybe will or won’t match the kind of thing that you want to do, but I’ve decided that I’m only going to cache my GET requests.

You can’t really cache POST requests, or,
>> Kyle Simpson: POST responses to POST request. I mean, technically you can, but that's a really bad idea to try to cache a POST request. GET requests are often more like retrieving some data set, which is actually what's happening on our homepage. It's loading the list of current POST IDs from the server, that's how that list is populated.

And that's the only GET API call I'm making. The other two API calls I make are POSTs. So this is effectively saying, for my GET API calls, I want to cache those responses. So if it came back successfully, and it was one of my GET API calls, I'm gonna put a clone of that response in.

So I'm literally putting my JSON response into there. Now, why would I be doing that? Most of the time you probably think, I don't want to cache my API responses because I want those to be fresh. Well, first of all, I'm trying the request every time. So my strategy here is request and then try cache rather than the other way around.

So if I'm online and I'm able to make a valid API request, I'll always get the fresh one. But if I'm offline, and this particular API is kind of critical to the home page experience because it won't list any blog posts if it doesn't succeed, I would at minimum like some old version of the list of POSTs.

Even if it doesn't have new updated data, it's better to have some data than no data at all. So that's why my strategy here was stick that particular JSON response in my cache. By the way, I don't have to use the cache for that, another way of doing this would have been to take that JSON data and say, stuff it in an indexDB record, for example.

And I've also done that. In a little while, we'll talk about using IndexedDB for things. But in this particular case, what I wanted to do was just simply cache that JSON response and then send it back as if it was coming live. So if it came through correctly, I cached it and then I return it.

Now if that fails, meaning I was not able to get a successful response, then I wanna check to see if there's a previously cached version. So that's what my cache.match is, and it's returning it. And if both of those fail, if I'm unable to request the API and I don't have a cached version of it, then I'm gonna simply return.

And in this case, my my function is notFoundResponse. That's like a network level 404. Because I know these are AJAX calls, we don't need to send any content, we can just send the 404, and tell it, that failed, you couldn't get it. So if you wanna see Not Found response, you can make your own responses.

So we can use the Response constructor, give it a status, give it a status text. You can literally even inject body content if you wanted to make up a real request, a status 200. You can do whatever you want here, but I'm making up a 404 response so that the page can know that the AJAX failed, okay?

So that's how I'm handling my APIs. I just decided I would try to always hit the server first. If not, fall back to the cache but only for the GETs
>> Kyle Simpson: What about for the difference between loading a JavaScript file versus loading an HTML file? In my opinion, there should be a different caching strategy, or a different service worker strategy involved.

For example, with pages, I kind of always wanna check and see if there's a new page at the server. If I'm online, I wanna go ahead and request the HTML. But if I am loading up the style CSS, and that's already in the cache, there's no particular reason to go ask for that new style CSS.

In other words, if I wanna push out a new style CSS, I'll handle that by updating the version in my service worker and refreshing the cache. Remember, I wanted everything to sort of be atomic. So everything is generally atomic except for my pages, I do want to allow those to give fresh content.

If I have a typo on my about page, I don't feel like I should have to reload the whole page just to fix a typo in that HTML. You follow where I'm coming from? So that's just the strategy I decided for here. Which is, and this is a trick, I'm looking at the request headers, if there's an accept header that is accepting text HTML content, then I know it must have been a navigation to an HTML page.

Cuz otherwise the browser wouldn't have put that Accept header in the request. So another way I could have done that is just hard code the URLs that I care about. But here, I'm kinda using the trick that I know that we're gonna be requesting an HTML page. Now, I have different kinds of HTML pages that there's some nuance here.

So these requests for login, logout, and add-post, they are much more sensitive because they're sensitive to whether or not I'm logged in or not. And especially the online/offline behavior I wanna be careful with. So right now, I'm just gonna leave those as a to-do, we're not gonna handle those.

Well, in our next section of discussion, we'll talk about how to handle those. But down here, if I'm in one of those regular pages like /about or /contact, again, my strategy is only do the fetch if I'm online. So that's similar to the one before, only do the fetch if I'm online.

Here, I'm gonna use the method and headers. I don't specify anything about credentials. So I don't need any credentials for any of the pages that I'm gonna be requiring, I only need credentials here. So I could have said credentials.omit, but it's not necessary unless you really need to say that.

I make my AJAX request. If it was successful, now here's a little nuance. I have my server setup that if it returns the friendly 404 page, right? That is the server saying that page doesn't exist but I'm not actually returning a 404 network response. I'm returning a 200 network response with a friendly for a 404 page.

I want for my service worker to know that we didn't legitimately find the resource, because I don't want to cache another copy of the friendly 404 page under some URL. Then I might have a whole bunch of exploded cache with a bunch of wasted stuff. So my server specifically appends a custom header called X-Not-Found in the case where it's sending out the friendly 404 page.

And I'm saying, if it's not one of those, if it's not the friendly 404 page, then put it into my cache. If it's the friendly 404 page, just display it but don't cache it, okay?
>> Kyle Simpson: And then, again, if we get here, it's because the network request failed or we were offline, so let's go ahead and try to see if it's in the cache.

And if it is in the cache, use it. And now we finally see, what do I do if both the request and the cache fail me? Well then, I just wanna go ahead and unconditionally serve up the offline page. That tells the user, I kinda don't know whether the page exists or not, but I know I don't have anything to give you.

So I'm gonna friendly tell you you're offline, try again when you're back online.
>> Kyle Simpson: All right, now if I'm not in one of the HTML page requests, I want a different strategy involved, I wanna check the cache first. So here I'm gonna do cache.match, and there's my request URL.

And if that succeeds, great. If that fails, then I want to check to see if I'm online. Cuz, again, I don't wanna make a request if there's no reason to make a request. If I'm online, I'll try to make an AJAX call. I know I don't need any credentials here.

If I succeed, then go ahead and update the cache with it. So in other words, I might have already had the style CSS. Well, if I already had the style CSS, then it would have matched here. But if I for some reason that wasn't in the cache, then I request it, go ahead and stuff it in the cache.

And if I get here, either we were offline or the cache failed and we were offline, in which case I give them another network level 404 response. Now, that's a little bit different than what I did with an HTML page. It's because we don't need to serve up the friendly 404 page to a request for CSS.

The browser, if it can't load any CSS, it ought to just get a 404, same thing as we did with the AJAX, make sense? So all of these things that I'm doing, these are all very careful thoughts that I had to have. I had to ask myself all these different things.

What would be the appropriate response in this case? And what would be the proper response? Point is, I have complete and total control. I can write as much or as little of that logic as makes sense for me. A lot of the frameworks that we talked about, or the frameworks that we talked about, like for example, Workbox, from my observation, they make a lot of these decisions for you.

You make a lot of very high-level declarative statements, like which strategy you want. And all these nuance kinds of things, like checking for a specific header from the server, that's not really a thing. So there's a trade-off here, as with all things, you can much more quickly get a service worker working without having to write all this if logic.

But then you don't have control in those different cases, which might mean that the user experience is acceptable or not acceptable, depending on your perspective.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now