 Hey everyone, my name is Philip Walden. I'm an engineer on the Chrome team working on web performance and service worker tooling And I'm Eva. I'm also an engineer on the Chrome team Today, we're going to talk about service worker from the speed and resilience perspective We look into how our decisions regarding service worker implementation can influence both positively and negatively our site's performance and we hope that by the end of the stock you have a really good understanding of all the trade-offs involved in Using this more and more mainstream technology. So Eva, I notice you said a service worker was mainstream. What makes you say that? Well service worker is now supported in all modern browsers, which means you can move from treating this as a pure Progressive enhancement and treated as a core part of your site's architecture Service worker can do many things, but in the stock will focus especially on its caching capabilities Often when we think about service worker and caching we usually associated with providing offline support After all, one of the main achievements of service worker was that we finally could get rid of the offline Down Azure and you know, send him to the well-deserved retirement But apart from that Service worker can also be a great tool for improving the performance of your online site Especially for your returning users When used right it can give you a serious boost in terms of speed on a repeated visit That's right And on the other hand when used incorrectly or without proper analysis It can actually hamper a site's performance or even derail the whole experience altogether So as developers, it's critical that we understand how a service worker affects the performance of our site As with any technology there are both costs and benefits and we want to maximize the benefits while minimizing the cost Well using service worker brings a lot of benefits in terms of performance in many cases It allows you to Overcome the network latency entirely for example, if you cash your entire up you don't need to go to the network anymore Also, if you have some cashed content, you can show it immediately Even if it's stale and look for the updates in the background at the same time It can also make your average requests smaller on average For example in the actual model where you're fetching just partial part of your page rather than the full html each time Finally, there are some more subtle benefits For example, we know that Java scripts need to be parsed compiled and executed Before it can be used this can take time. So engines like v8 use some heuristics To see if they can actually store the bytecode from these faces to be used on a repeated visits to avoid this cost Now in service worker the chances of a repeated visit are pretty high so it can opt in into that Optimization automatically for you. This means that it stores the scripts as bytecode by default making the repeated visits faster All these are really good performance reasons to implement service worker on your page But it's not for free. Phil what are the costs of running service worker on your page? Yeah, so the first and arguably most often overlooked cost of service worker is that it can take time for the service worker process to Start up if it's not already running and this can happen if a user hasn't visited your site in a while Let me show you what I mean Consider a basic network first strategy in which the service worker just forwards the request from the web app onto the network And doesn't touch the cache at all Since this web app web app is running a service worker every request then has to go through that service worker And if the service worker process isn't currently running the web app has to wait for the browser to spin it up before it can make any request So let's take a look at the total time it takes to make a request in various scenarios In the case where the web app isn't using service worker The total time is just the total network latency in the case where the web app is using service worker And that service worker is already running There's a little bit of extra cost because it has to go through the service worker The request has to go through the service worker, but that cost isn't usually too high However, in the case where the service worker is not running and needs to boot up that startup time can really delay the response So in cases like this where the service worker is actually not running on the page How long does it usually take to boot up? So that's a good question and the honest answer is it depends on the user's device But fortunately, there's an easy way you can measure this cost yourself So this code here uses the performance timeline to get performance data for a particular URL If the request for the URL went through the service worker, then the worker start property on the performance entry Will mark the moment right before the service worker was run And the request start property will mark the moment the service worker received the fetch event So the difference between these two timestamps is the total time it took for the worker to start up And if the service worker process was already running this time will be usually zero or close to zero And so I actually measure service worker setup time on my website philipwellin.com and here's what I found when looking at my own data When the user visits my site for the first time or sorry for the first time after installing the service worker It's already running only about 25 of the time That means 75 of the time the service worker is not running and needs to take some time to start up For those cases, I found that on desktop It's usually between 20 and 100 milliseconds to start up But on mobile it can be more like 100 to 500 milliseconds and at the 95th percentile Sometimes it's more than a second So let me reiterate these are stats from my website the numbers you see might be different But this should give you kind of a general idea of what's possible Another cost of using service worker is that the cache reads aren't always instant And this affects any caching strategy where the service worker has to wait for the cache to either miss or error before it can go to the network We saw before that a network for strategy can be slow when you're not using service worker at all But what about strategies that use cache or start with the cache anyway? A cache for strategy will initially look for a response in the cache And if one isn't found if one is found it'll be used But if it's not found or there's a timeout or error like I mentioned it will fall back to the network So here's how the performance of cache for strategies break down in different scenarios The most common case is going to be the one at the top which is very fast when there's a cache hit But this is not the only possibility there could also be a cache miss there could be a slow cache You could have a timeout remember There's also the possibility that the service worker isn't running and so then that could delay it as well All of these bad cases could happen at the same time So well, it's definitely possible to have a cache for strategy That's faster than not using a service worker. Look at how many of these examples end up being slower So i'm wondering how likely is it that this slow case is actually occurring. Is it measurable somehow? Yeah, so this is also measurable It's a little bit trickier than the last example I was showing so this The same way uses the performance timeline And you can look at the entry's transfer size property if the transfer size is zero But that means the request either came from the cache or came from the service worker For requests that you know are being handled by the service worker because you set up a route for that URL You can look at the time between the response star property and the request star property And see like the total strategy time Of course, that doesn't tell you if it was just handled by the cache if it was handled by the cache and the network If you need more granular timing data into that Stuff then you have to add these performance marks kind of in your service worker itself You can use something like performance dot now And then post message the data back to the window. It's a bit conky at the moment to be honest But we have a new api proposal for fetch event worker timing that should make this easier in the future And it is a proposal right now So if you want to offer feedback on the design go to this short link here on the slide And we love your your feedback and you can chime in So the last cost that we want to point out is that requests made from within the service worker Can sometimes compete with higher priority requests On the window and this the cause of this is usually over aggressive pre caching or pre caching before the window has finished loading All of its resources So for example, if you're pre caching literally every single asset on your website You could potentially get to a situation where those pre cache requests get queued ahead of more important requests that the user needs So while apis like priority hints can solve this issue somewhat the recommended approach right now Is just to wait to register your service worker until after the load event Okay, thanks phil for the thorough walkthrough through the costs So once we know all this and we know how to measure it How does this translate into the design of service worker? Well, usually There are three sources the service worker gets content from either from network either from the cache Or it can also generate it on the fly for example by using some, you know, template logic When designing service worker your role is to find a combination of these three sources That is the most efficient for the use cases on your web pages This is the serving strategy of service worker Of course in order to use anything from the cache We need to populate it first with the resources first and this is the caching strategy of service worker Taking all these aspects into account can be quite daunting But fortunately there are tools that make it easier For example, work box is a set of libraries that make it easy to cache assets and take advantage of service worker features and related apis In this talk We'll focus on general design principles that you can implement yourself in service worker But we will also call out some of the work box features that can help you out in some common scenarios Okay, so let's start with looking at the serving strategies for service worker implementations And given what we said about the costs that can be associated with using service worker You're probably wondering is it possible to avoid these costs entirely and make sure that my site actually loads faster than it would have without the service worker So the first and arguably most important step in building a fast site with service worker Is an understanding of which requests are most important to optimize So broadly speaking there are two types of requests navigation requests and resource requests navigation requests are for your full html pages And resource requests are for the assets like javascript css and images that those pages then reference So in my experience with talking to other developers about service worker implementations I found that most people are generally pretty good at responding to resource requests from the cache But unfortunately, I don't see a lot of people responding to Navigation requests from the cache and that's really too bad because navigation requests are typically where the biggest performance gains Can be made So here's why the key difference between resource requests and navigation requests Is that navigation requests are likely already being cached by the browser In addition, you can already use apis like link rel preload to warm the htdp cache for future requests So if all your service worker is doing is pre caching static resources You're essentially just recreating what the browser is already doing for you Navigation requests on the other hand are completely different in general It's not recommended to put caching headers on pages that you navigate to Because you know obviously the content might change, but the url doesn't and so that can get you into trouble Which means that navigation requests don't benefit from the htdp cache in the same way that resource requests do They also don't work with apis like link rel preload And to top all that off navigation requests are typically the ones That will encounter a service worker. That's not running Whereas resource requests by the time the service worker starts up from navigation requests It's already running and so those kind of work just fine. So don't get me wrong I'm not suggesting that you ignore resource requests and don't cache them You should cache them because that can give you more control and you can get the bytecode optimization that avian mentioned But what I am suggesting is that if you want your site to be as fast as possible As possible you have to respond to navigation requests from the cache as well So here are three practical and concrete ways you can speed up navigation requests And avoid most of the service worker cost that I mentioned earlier First as I just said respond to navigation requests from the cache Even if you eventually need to go to the network you should respond with something right away So the the user understands that that is happening that the request is working A simple way to do this with work box is to use either a cache for strategy or a stale while revalidate strategy Personally, I like stale while revalidate because it gives you an opportunity to check for updates in the background And then you can notify the user if there's new content And in terms of performance as you can see responding from the cache is generally faster when not using a service worker And that's even true in cases where the service worker is asleep and needs to start up or in cases where the cache is slow The only situation it's worse is when there's a cache miss and you have to go to the network Anyway, but that's to be expected So I know what many of you here are probably thinking There's absolutely no way that I can respond to all network requests Navigation requests with cache content. I need my content to be up to date I need it to be fresh and this is true. Many apps just simply cannot provide value With stale content. They have to have fresh content But just because you need to fetch Something from the cache or from the network doesn't mean that you need to fetch the entire html page from the network So my second tip is that when the network content is truly required Fetch just the minimum amount of content you need and then combine that content with other Parts of the page which should already be in the cache and even better is if you can combine that content In the form of a streaming response to the user. So let me show you an example of what I mean by that A lot of html pages have a structure that looks something like this You have kind of a head section a header navigation sidebar footer and a lot of these sections of the page repeat On every single page throughout your site The only thing that changes is often a single content area like you can see here So in these types of situations clearly It's more efficient to just fetch the stuff that's changed rather than fetching all of that stuff every time If you've ever built a single page application, you're probably kind of familiar with this concept Though the streaming part might be new to you So here's a visualization of the performance breakdown between between traditional network responses and a streaming response that combines Never content with cache content So in the example on top Since the network request is for the full html page You can see it takes a long time and in the example on the bottom The network request is for a smaller part of the html and so there's less data to be transferred So it will typically take less time In addition the cache content can be fetched in parallel with the network content So it doesn't add to the total time it takes to make the request And lastly, and this is the best part about streaming Since it is a stream We don't have to wait until all the content is available to start sending something to the user As soon as we have the cache content from the start of the page the header section We can send it to the user right away and then we can just add to the stream once we get more network content in later So the overall experience is a much faster time to first byte and then a faster time overall as well If you've never used streams before you might be a little bit scared of the idea You might think it's complicated, but actually with workbox. It's really easy So what you do is you you know just register a route like you would do with any workbox strategy And then you invoke the strategy function from the workbox streams package The strategy function takes an array of other workbox strategies And each of these is expected to resolve to a response that you then sit together and workbox stitches together in the form of a stream In this case, I'm using cache first for the the header part and the footer part And then I'm using network first for the content so I can make sure that it's fresh And and that's really it workbox takes care of merging this content together in a stream And if the browser doesn't support streams, it's automatically falls back to a single text response And because the strategy requests less content from the network and because it can read from the cache in parallel It's typically quite a bit faster than the node service worker case Of course the actual speed differences will depend on you know the size of the content area relative to the entire html page And that will vary from site to site But in general streaming cache content with network content is one of the fastest ways to respond to navigation requests And I say one of the fastest ways because there actually is one more optimization that we can make to get this even faster So the last technique for speeding up navigations that I want to talk about is a new api called navigation preload Which allows you to make the navigation request In parallel with the service worker starting up essentially eliminating that service worker boot up cost that I mentioned before So by now you've probably seen this chart many times you understand that The service worker boot up time can extend the navigation request So with navigation preload what you do is you just do these requests in parallel And the way that this works is the browser sends that sets this navigation worker Sorry this service worker navigation preload header on the preload request And then that allows your server to respond to this request as it would have had the request come directly from the service worker itself So for example, if you're using the streaming partials strategy that I just discussed You could respond to the navigation preload request the same as you would have if it had come from the service worker To use navigation preload, uh, it's relatively easy. All you have to do is Enable it you probably want to feature detect it first, but then you enable it At any point in the life cycle really, but it's often best to do it in the activate event And then once you've enabled it fetch events for navigation requests will have access to a preload response property which you can then use however you want And then looking at the performance of this technique, you'll see how navigation requests with preload with navigation preload Are even faster than the already fast streaming example Um And you know one last thing I want to say about navigation preload is that you really only want to use it in cases where you You know, you're going to have to make a navigation request or sorry a network request on navigations If you can use just the cache first strategy to respond to navigations Then that ends up being faster and then you waste the network request So in general only use it if you know that you have to use Content from the network. So to summarize everything I've said so far Even though there are definitely some costs associated with using service worker with the proper serving strategy You can easily overcome these costs and you can end up with a even faster loading site than what you could have done without service worker Okay Now let's talk a bit about the caching part of the service worker When we think about cache management, we usually want to achieve the following We want to store the right resources at the right time while controlling the overall size of our application We definitely want to prevent quota overflow because as developers We do have quite a bit of storage space on users device, but it's not unlimited So we need to stick to that and we also want our resources to be as fresh as possible Which means we need to have efficient updates Now When it comes to right resources, it's good to understand what you actually want to put in the cache in the first place Resources are a little bit like food, you know There are the critical ones really important ones like some html Core scripts or basic styles. This should really get the highest priority in terms of cache Then there are non-critical ones. For example Images that are not visible straight away or some big media files or some additional widgets We catch them on a best effort basis And finally there's trash which means stuff that should not be there in the first place This is this is the bloat the unnecessary parts of your page like unoptimized images that code unused script and so on Why do we have trash on our pages? Well, because we're humans and sometimes our projects are simply not perfect This moment when you're considering how to shape your caching strategy for service worker Is a great time to stop for a while make an audit of your page and get rid of all that unnecessary parts Before they end up clogging your user's device So reviewing your app and understanding which resources are critical and which are not so critical We really help you later in designing and updating your cache in an efficient manner Now what about timing? Usually we cache assets either in the install event of service worker That's usually pre caching or later during the runtime of our application. Let's compare these two So pre caching is very similar to installing a ready made package with your app It's great for caching the critical content Since we know it's most Probably going to be needed anyways It's relatively easy to implement and to manage and to update because you can just you know replace the whole package with the new version when needed and also because the size of such package is known beforehand On the other hand when using pre caching you need to make a lot of arbitrary decisions About what your user is going to need even before they start interacting with the page Which might lead to the situation where you cache Many more resources than necessary Also, as we mentioned before it can cause network congestion and compete with the other network requests from the window Runtime caching on the other hand, it's really great for non critical assets because we you can draw conclusions from your user's behavior For example cache only images. They already accessed or show different cache different parts of your app depending on the user's entry point On the other hand the problem with runtime is that you need to be really careful about updates Because assets might end up in cache at different moments in time For example if resources depend on each other like you have a script that depends on markup And you end up caching compatible versions of them you run into trouble. So you need to be really careful about versioning Also important thing is that in this case the cache will grow over time It's the size is not known beforehand So the user as the user interacts with the app the size will be different Here's an example. This is a very simple e-commerce app. I built recently If I cache pages in this app at runtime And the home page is fully cache. It takes about 150 kilobytes with all the images But later when the user navigates to a new category like accessories page It gets add to the cache and the overall size grows to 300 kilobytes And so on and so on as the user interacts with my app the overall size grows If the app is really big it might be really hard to predict ahead of time How much space it will take on users device? This is why it's so important to control the size of your app throughout the runtime Phil can we do it programmatically somehow? You can If you ever need to check the your app's current kind of storage usage You can use the storage manager api Which has an estimate method that returns both the total quota as well as the current amount that's being used Well, it's really cool that we can estimate that because It allows us to proactively control the app size and prevent quota overflow Quota is limited and depends both on users device and also on the amount of currently available space on the device So you can't just throw assets into the cache and assume it will never fill up You always need to have a plan on how to remove old or unnecessary assets After all you don't want a situation where you can't update some critical script because your cache is full of you know CAD videos Here are some things that can help you to stay below the quota as we mentioned before You can store partials of your pages instead of full html to avoid duplication and save some space You can also separate your critical and non-critical assets into different caches with different names So that you can evict the non-critical ones when needed without touching the rest Finally, you can also cache some resources Only if there is plenty of space like conditionally or you can put size or maximum number of entries constraints on your cache I think this is something workbox can help us with Yeah with workbox You can easily manage the rules for how and when cache entries should expire with the cache Exploration plugin and you can use it with any of the workbox strategies So you configure both a max number of entries per cache or a max age for each entry you can also configure The plugin to automatically purge all event all entries if there's any kind of quota error or anything like that Which is usually a good thing to do when you have a non-critical asset cache Okay, now a few words about updating the cache The simplest solution is what I call the nuke approach Which means you clear all your caches and start fresh every time your service worker updates It's very easy to implement, but it's not very efficient nor kind to your users data plans So you should be more granular about what you update and when and if you want to be more granular You need to properly tag your assets so that you know which ones are compatible with each other You can use the content based hashes in the file name of the given resource Or provide revision data on each asset so that you can manage it in service worker later on This process can be very error prone when done manually. So fortunately, we have tools to help with that as well Yeah with the workbox workbox pre caching package. You don't have to manually Manage the update process yourself at all it has an asset manifest that maps file URLs to their revision hashes And that allows it to remove old assets and fetch new ones without having to touch any of the unchanged assets It makes the upgrade process really efficient Workbox also has both gulp and webpack plugins and as well as the cli so you can easily generate this asset manifest yourself As you can see workbox makes a lot of things easier for us as developers So that we can focus on what matters most and that's the user And you know users can really vary it they can come of they can be of different background They can use our app at home or on the go They can use more or less advanced devices or have different access to data and connectivity There are some things we can do to accommodate those differences First of all when working on performance never assume the environment you work in is representative of your whole user base For example, you should always throttle your network to 3g speed when testing to get a more realistic feel for your app performance Secondly keep in mind those underpowered devices with little storage and really control the size of your app Remember that the overall size of your app might grow over time if you use runtime caching and plan accordingly Also, sometimes there are actually explicit hints from the user that you can use in your decision making For example, you can refrain from speculatively pre caching future resources if the data saver mode is turned on When user enables this feature in chrome did save data header is being sent with each request so you can detect it And for example refrain from aggressively pre caching a lot of future assets Similarly, you can use the network information api Effective type methods to differentiate your strategy based on the current network condition of the user Finally you can also consider scenarios where you give the user the full control over the experience For example, you provide save for later button where user can explicitly opt in and decide to get something stored for future use Putting the user first can really benefit the quality of your app, especially in the long-term development horizon So to wrap up I know we've presented a lot of content today And we don't expect you to remember everything If you want to learn more about service worker best practices We've launched a new section on web.dev with content dedicated to building fast and resilient web applications with service worker So definitely check that out Also, we've just released a v4 beta of work box with lots of cool new features And we love your feedback on github before the public release And finally just a few key points. We want to leave you with first definitely have a plan You can't just assume that adding service worker to a site will magically make it faster Because without an optimization plan it probably won't Second don't just reinvent the http cache inside of your service worker And don't just cache static resources if that's all you're doing you're not actually optimizing your site for service worker And you might even be making it slower Third remember that navigation requests are the most important requests to optimize And you should always try to respond to them from the cache fourth measure the real user performance of your implementations And make future performance decisions based on data. Don't just guess Fifth control the size of your app and how much you store on the user's device And last but definitely not least respect the user respect their data and respect their preferences So thank you if you have any questions feel free to find either of us afterwards Or hit us up on twitter and ask questions. That's it. Yeah. Thank you