 Users have high expectations for the apps that are installed on their phone or computer, and progressive web apps make it possible for you to deliver on those high expectations. I'm Pete LePage, a developer advocate on the Chrome team. I want to show you a few tips and techniques that you can use to ensure that you can deliver on those high expectations and how you can use analytics to prove the investments that you made are paying off. Let's dive in. When was the last time you saw an app crash because it was offline or had a crappy connection? You probably haven't. If you've installed an app, you expect it to work at least a little bit, no matter what the network connection is like. And this is true for installed progressive web apps as well. But let's be honest, there's a lot to think about when considering what your offline experience should look like. What should happen if the user tries to start your PWA without a network connection? What if they lose the connection midway through? Once you've figured out what the behavior should be, implementing a full offline experience can take time, especially if you're trying to retrofit it into an existing code base. One way to jumpstart your offline experience is to provide a custom offline fallback page that's shown when the user is offline or loses their network connection. Some sites, like the Lacoste site in Germany, have even put a game on their offline page to keep users engaged and on their site. When the network comes back, they're good to go. So how do you create a custom offline fallback page? First, let's take a look at the HTML for the offline page. To keep things as simple as possible, inline everything you need for the page. CSS, JavaScript, any images, anything, all of that stuff should be inlined. Your offline page should also be fairly simple. Don't include links to other parts of your site unless they'll work offline. And skip the links to your social media sites. Remember, the user's offline, so sending them anywhere else probably isn't going to work. When the network comes back, the page should reload automatically so the user can continue what they were doing. To do this, listen for the online event. If it's fired, reload the page. But because the online event isn't always accurate, periodically make a fetch request to check the network state. If it returns a 200, reload the page. Now, let's take a peek at the service worker. Use the install event as an opportunity to cache the offline page. Because it's self-contained and everything is inlined, it's the only file that needs to be cached. The fetch event handler is where most of the magic happens. But it should only handle navigation events. There's no need for it to send the offline page for images, CSS, JavaScript, XHR requests, or anything else. In fact, that's why we can use the fetch API in our offline page to check the network. It's not a navigation event, and it's not handled by this event handler. If it is a navigation, use respond with to try and get the page from the network. But if it fails, return the offline page from the cache. Let's see it in action. When I navigate to the page for the first time, I see the service worker's installed, and it has cached the offline page. Now, if I go offline and reload, I get the offline fallback page. In DevTools, I can see it periodically making that fetch request to check the network status. And when it does come back, the page refreshes, and I'm back in business. There you have it, a quick offline fallback page. The full code with all the comments and everything you need to literally copy and paste it into your PWA is on web.dev. It also includes an important optimization to enable navigation preload. One other thing to consider is that the page doesn't have any analytics, so you won't be able to measure how often your users are seeing it. For that, I recommend Workbox. They're recipes for comprehensive fallbacks and built-in plugins for offline analytics. Does your app have a few common tasks that users frequently want to jump into? For example, Twitter has added shortcuts to make it easy to compose a new tweet, explore what's going on, see notifications, or send a direct message. Adding shortcuts to your PWA only requires updating your web app manifest file by adding a new shortcuts property. For each shortcut, you'll need to provide the name that should appear for the shortcut, the URL that should be opened when the user launches it, and, optionally, an array of icons, including the source, sizes, and type for each icon. When you're ready to test things out, take a peek in the manifest pane of the application panel and dev tools to verify that everything looks right. Shortcuts are one of the easiest things that you can do to provide a more integrated experience for your users. You can use them on Android and Windows with more platforms coming in the future. There's currently no built-in mechanism for localizing your web app manifest. If your users speak different languages and you want to serve a localized experience, you're kind of out of luck. But not really. If you want to localize your app name, shortcuts, or icons, you'll need to create separate manifest files for each language. For example, to offer Squoosh in English and French, you'd create two separate manifest files. That part's easy enough, but how do we tell the browser about the localized manifest file? For that, I recommend using the accept language HTTP header on the server to decide which manifest to send. This way, the browser will always ask for the same file name, but will get different content depending on the user's preferred language. To do this with Firebase Hosting, create a localized files directory and drop the localized manifest files into the appropriate language folders. Then add an i18n property to the firebase.json file, and that's it. The server will serve the appropriate file to the user based on their preferred language. English gets the manifest from en-all, and French gets the manifest from f-r-all. The configurational vary for different servers, but the concept remains the same. If you want to take this a step further, provide a language picker within your app that sets a cookie with the user's selection. Then serve the localized manifest with the cookie taking priority over the accept language HTTP header. One advantage of this implementation is that if users change their preferred language later, the manifest remains at the same path. And in many cases, the PWA will be automatically updated based on the user's preferred language. To learn more about how Chrome handles changes to the manifest and what properties will trigger an update, check out how Chrome handles updates to the web app manifest. One question I'm often asked is how you can measure the effectiveness of your install experience and what methods work best. Having this data is the first step to improving your install rate. Let's take a look at how we did it for Squoosh and how you can do it for your PWA. First, let's measure how often users are shown our custom install experience. You've seen the before install prompt event before. It's fired when the PWA meets the installability criteria. When this event is fired, it means that we can show our own custom install experience. The install source variable will help us to understand where the install came from and how effective each source is. It'll also tell us if the install came from the browser or from our custom install experience. This is especially important if there are multiple custom install experiences. For example, a button in the header, some kind of promotion that's shown in line with other content, and a button that's shown at the end of a critical user journey. In the before install prompt event, log the event to analytics. For consistency, all of my install events use the same category, PWA install. In this case, the action is promo shown, and I've set non-interaction to true since the event wasn't generated by a user interaction. Great. So now we can measure how often the user shown the custom install experience. Let's measure how often the user clicks on it and whether they complete the install. Like I said earlier, we want to know where the install came from, so we'll set the install source to header button. Each install element should have its own unique ID. Then, as usual, call defer prompt.prompt to start the install and show the browser install dialog to the user. Wait for the user to respond to that dialog, then log the event to analytics. Like before, the category is PWA install, but now the action is promo clicked. Use the install source for the label to identify which custom install element the user clicked to start the install, and set the event value to either 1 or 0, depending on whether the user clicked accept or dismiss. In analytics, this will tell us what percentage of users completed the install for that specific element. Finally, we need to listen for the app installed event. This tells us when the PWA has been installed, including installs from the browser. If the user has multiple tabs or windows open, the app install event is fired in all of them. So to prevent duplicate install events from being logged, only log the event if the page is visible. Next, check the install source. Did it come from one of the elements in R UI or from the browser? Let's take a look at the analytics for Squoosh to see how this looks. Under events, then top events, I'll click on PWA install, and it'll show me all the actions for that category. I can see the number of times the custom install experience was shown, how often one of them was clicked, and can dive down to see which element was clicked, and how often. In the case of Squoosh, we only have one install button. But looking at that event value, I can see that after clicking the install button, 64% of users went on to complete the install. And looking at the details for the installed action, I can see what percentage of our installs came from our custom install experience versus which ones came from the browser. Now that you know how effective your install experience is, you probably want to know how to differentiate users who have installed your PWA versus those who are using it in a browser tab. To measure this in Squoosh, I created a user-scoped custom dimension called display mode. Once Squoosh is launched, I use match media to check the CSS display mode, then set the custom dimension to either browser or standalone. This also works for Safari users who have added your PWA to their home screen, which means you don't need to use navigator.standalone anymore. I also want to set the dimension to standalone in the app installed event. Otherwise, after the install, those users would still be counted as browser users. Let's take a peek at what this looks like in analytics. I've created a custom report that looks at a few key behaviors. For example, the number of sessions per user, average session duration, events per session, conversion rate, which is how frequently someone downloads a compressed image, and a few others. Next, I've broken things down by the custom dimension I created earlier, display mode. Looking at the report, I can see users who have installed Squoosh are more engaged than users who use it in a tab. They come back to Squoosh more frequently, they spend more time in the app, and they have higher conversion rates. What counts as a conversion in your app will be a little different, so you'll have to adjust the report to suit your needs, but the concept is the same. There you have it. I've shown you how you can provide a custom offline fallback page, avoiding the offline dyno, and ensuring your PWA remains installable with the new PWA installability criteria that's expected to land in Chrome 93. How you can make it easier for users to start common tasks with app sortpets. How you can provide a localized experience for users who have installed your PWA. How to measure the effectiveness of your install flow. And how you can use analytics to prove to your boss that investing in the web is always a win. Thanks for watching. You can check out these links for more details and the actual code I used in the examples. I'll see you soon.