 As developers, we care about the experiences that we deliver on the web. We've put a lot of time and effort into our current projects, and building a progressive web app doesn't necessarily mean that we need to start from scratch. We need insight into areas for improvement and tactical fixes that could be applied to our existing code bases. I'm Pete, a developer advocate on the web team at Google. I want to show you how you can use the new audit features in Chrome DevTools to improve your apps and take the first steps towards transforming them into great progressive web apps. Our starting point is a web app that uses the iFixit API to access repair guides. The initial implementation is a single-page app that uses client-side rendering. While this project uses React, the concepts that we're going to talk about apply to any framework or vanilla JavaScript. In fact, I'm not much of a React expert myself. If you want to see the code used in this video, you can grab it from the link in the description. Let's take a look at the initial experience in Chrome. Alright, that looks about what I'd expect. Firefox? Alright, looks good. And finally, Safari. I'm going to do something a little different here, an intentionally disabled JavaScript to see what happens when there's no JavaScript. There's no content. That makes sense because our app is currently rendered on the client, so if JavaScript is disabled, we don't get any content. So now that we have a feel for what the app does, let's try and figure out how we can improve the current implementation. We're going to use the new audit panel in Chrome DevTools, formerly called Lighthouse, which will run our app through a series of tests covering important areas for progressive web apps. The same tests are also available as a command line tool or a node module that you can add as part of your integration tests. We'll run it now to establish a baseline, and it'll give us an idea of what's working well and what we can improve. Let's see it in action. I'll pop over to the audit panel and start my tests. The audit reloads the page several times, capturing traces on each load so it can analyze how long each load took, when it became interactive, what happens when there's no network, and so forth. It usually takes about 60 seconds to test my app served for my local device. While Lighthouse will identify areas for improvement, it's not the end destination. You shouldn't spend all your time trying to get a perfect score. Ultimately, the experience that your web app provides to your users is what matters the most. In addition to running Lighthouse, you should try your web app across other browsers, other devices, OSs, and network conditions as part of your normal testing and release process. With that caveat out of the way, let's dig in and look at the results. Well, I told you earlier not to necessarily focus on the numbers. Looking at our scores, we've got one red and three green. There's a lot I can do to improve my progressive web app score, and I think I can get the other numbers up too. My goal is to make sure that all of the scores are green, and I'm going to aim for a score of 90 or better. There are a lot of failed audits here, which explains our low score. There's no service worker, and it doesn't work when offline. There's no content when JavaScript is disabled. And we haven't done anything to make the app work well on the device, like adding a manifest and setting the meta theme color. We did have four test paths. Well, really only three. Since we're running on local host, the user's HTTPS is a freebie. The page loader is fast on 3G. We've set the correct meta viewport and sized everything correctly to fit. The results also include some recommendations on manual tests that we should run, things that it's not able to verify itself. In terms of performance, it's good, but I think we can do better. The big thing I'm seeing here is that our first meaningful paint happens at 3,700 milliseconds, and our speed index is almost 4,500. By the way, if you're not familiar with some of these terms, you can click on the test results for an explanation of what they mean and how you can make them better. In opportunities, it gives us a few suggestions on things that we could do to improve our performance, like enabling text compression, reducing some of our render blocking styles, and dealing with some of the off-screen images. We can also check our critical request chain size. In this case, it's only three. We're probably okay. I could hyper-optimize this, but there are more important things to deal with. We've done well on our accessibility checks, and there are a few things here that we could change, but again, since it's already over 90, I'm going to call it good. We're doing well on our best practices. Again, it's green. We could try and get these numbers up, but our priorities should be on the other scores that have more room for improvement. Let's look into server-side rendering. Server-side rendering means that the browser gets a fully populated and functional page in the initial HTTP response, rather than having to rely on multiple requests to get the content on-screen. Equally important, it means that our web app will work on browsers that don't fully support JavaScript or when it's been disabled. And if we accidentally ship some bad code, it won't completely break our experience. Not every web app will be able to run with full fidelity with JavaScript disabled, but in our case, this should be fine. It's fairly straightforward to add server-side rendering to our React application using universal JavaScript shared between the client and the server. I'll use Express, a node-based web server which plays nicely with the React router. While we're talking about React, other frameworks like Angular, Ember, and others have their own solution for server-side rendering. First, I'll add a serve task to my gulp file that starts the Express server. Then, create the server using Express, React, and a few other components that I'll need for the server. And I need to update my index file to import the React content. So now that we have the server-side rendering in place, let's try it out in Chrome and Safari to see if it had the desired effect. I don't expect anything to change here. Chrome shows the server-rendered version, and it looks exactly the same as it did before. And how about Safari? Remember, JavaScript is disabled here. This time, it works because the content is rendered on the server and JavaScript isn't required. Let's see how adding server-side rendering affects our score. I'll open DevTools and start the audit. This takes about 60 seconds. So with a little bit of video magic, we're done. Adding server-side rendering to our app affected both our progressive web app score and our performance score. Our progressive web app score jumped from 36 to 45 and our performance score went from 79 to 91. Since we only added server-side rendering, I wouldn't expect much to have changed here. But still, we have a lot of failed audits, but the key thing is we now have content when JavaScript isn't available. Let's look at why we got that jump in performance. The biggest thing that changed is our time-to-first meaningful paint dropped to 860 milliseconds and the speed index dropped from almost 4,500 to 2,000. Our opportunities to improve performance have also now changed. It looks like we could save some time if we used lazy loading to postpone some of the off-screen images. We'll save that for a rainy day. Let's add a service worker to make it work instantly and reliably, even when the network isn't. First, an important reminder. We need to treat service workers as a progressive enhancement. Not all browsers support service workers yet, so we don't want to degrade the experience there. And even on browsers that do support service workers, the very first time you visit a page, they won't have the service worker installed yet. We're going to adopt the app shell plus dynamic content model, which works really well with single-page apps. The shell, our HTML, JavaScript, and CSS is loaded directly from the cache on the local device, bypassing the network. Taking the network out of the critical path is the surest way to get reliable, fast performance. We can then use runtime caching to handle requests for our dynamic content from the iFixit API. Under the hood, I'm using a tool called SW Precache, which will generate the service worker and implement the caching strategies for me. SW Precache is an early version of Workbox.js. It doesn't have all the flexibility and power that Workbox has, but it'll get the job done for today. First, we need to import SW Precache into our gulp build script. Then create a new gulp task that will generate the service worker to cache our files. And finally, we need to register the service worker. Now that we have the service worker in place, let's run the audit again and confirm that it had the intended effect. A little bit more video magic and sweet. By adding the service worker precaches our content, our progressive web app score went up by almost 20 points from 45 to 64. We had a 5-point jump in our performance score as well. We've improved the reliability of our app by adding a service worker. Our app will now respond instantly and reliably no matter what network conditions exist. Adding a service worker also helped to improve our perceptual speed index from just over 2,000 down to 1289. After you've added a service worker, it's always a good idea to pop over to the network panel and take a look at all of the network requests to ensure that your service worker is handling all of the requests that it's supposed to. Alright, that looks good. And I love those super short response times. While we're in DevTools, let's take a detour to another panel that's super important, the application panel. This is the central location for debugging everything related to your service worker. We can confirm that the new service worker is running without any errors. Here, we can force it to update, simulate an incoming push message, simulate a sync event, and unregister it. We can also use the various checkboxes at the top of the page to automate common debugging tasks. The offline checkbox lets us simulate network failures so we can confirm how our service worker behaves when there's no network. Update on reload ensures that when we make changes to our service worker during development, refreshing the page will always put the new code in effect right away. And finally, bypass for network skips the service worker's fetch handler, which disables the cache and forces all network requests to go straight to the network. Alright, let's address the missing application metadata. This metadata controls how your web app looks and behaves when it's been added to the home screen. Remember that manifest that we talked about in a previous video? Well, we need one of those. But we also want to provide a great experience on Safari for iOS. To ensure that we provide the largest range of browsers, we'll need to add the metadata using the web app manifest and the equivalent meta tags. Let's take a look. You can see that we've added a 512 by 512 pixel icon. If you're only going to add one size, make sure it's at least 512 by 512. Here's the manifest that we talked about earlier. We've added icons using the link tags to give Safari the information that it needs. We've added a link tag that points to the manifest, set the theme color, and added a meta, Apple, mobile, web app capable true to tell Safari on iOS that it can start in full screen. Let's test the home screen bookmark behavior on iOS Safari using the iOS emulator. Yeah, that looks good. So now that we have a service worker, a manifest, and everything in place, let's run the audit again and confirm that it had the intended effect. All right. With those few changes, our progressive web app score is up to 91 and our best practices score is up to. At this point, all of my scores are green and above 90. I'm happy. The only outstanding issue is that we're missing a redirect from HTTP to HTTPS. Since we're running on local hosts, we can safely ignore that one for now. Our performance still looks great. The time to first meaningful paint is under one second. Time to interactive is about three seconds and the perceptual speed index is just barely over the target of 1250. All of the accessibility checks look good. We're following all of the best practices, except for using HTTP to since this is running on my local dev machine. I'm not too worried about it right now. I can deploy this to a server with HTTP to later. Let's take another look at the application panel and dev tools in the manifest tab. We can see that the manifest is properly linked and we can see the app name, start URL, colors, and icons. Switching to the service worker tab, we can see that the service worker is up and running without any errors. And in the cache tab, we can dive in and see what's been cached by our service worker. As developers, our journey is never over. New features like constructable response streams present all kinds of new opportunities. I hope you're excited about the new audit panel and dev tools. Remember, the goal is to radically improve the user experience of your app and this is just one tool that you can use to do that. You can find the slides for this video linked in the description below. If you're watching this on a computer right now, why not try running the audit on this page to see how it does? Up next, we'll dive into how you can use AMP to create fast first load experiences.