If you're like me, you like to keep at the relative forefront of frontend web development, but trying to stay on the bleeding edge is exhausting. I know Vue and React and ES6; I haven't touched broccoli, next.js, or whatever else is the newest hotness. I can't. I let other people spend their time there and I just watch for what sticks.
Within the last year or two I've watched references to service workers and PWAs, or Progressive Web Apps, go from never-heard-of-them to every-other-tweet. So! Let's go learn. What is a PWA? What's their history, purpose, and value? Why do we care?
NOTE: Service workers, a key component of PWAs, are not supported in all major browsers. Check out Is ServiceWorker Ready?; at the time of this writing, Chrome and Firefox support SWs, while Safari and Edge are developing support.
Let's start by defining what a PWA is. If your first thought was "a web app using progressive enhancement," you're already halfway there.
Google—who originated and popularized the concept—defines a PWA as such: "A Progressive Web App uses modern web capabilities to deliver an app-like user experience." That makes sense, but obviously it means something more specific than just that. The concept of the PWA also includes some specific definitions of how PWAs should provide that app-like user experience.
Like many modern rich web applications, PWAs try to create a user experience that is as similar to a native app's as possible. But unlike many rich web applications, PWAs should work offline and on low-quality networks, and they should be "installable" and stay functional and connected using technologies like service workers and push notifications. They also must be responsive and progressively enhanced, meaning they need to work on less-capable devices, even if some niceties may be missing.
Essentially, PWAs are about using web technologies to mimic the best features of native apps: speed, connectivity independence, data synchronization, and use on any type of device.
Here's the full list that Google uses to define a PWA:
Source: Your First Progressive Web App
The team developing the PWA concept also added (in What, Exactly, Makes Something a Progressive Web App?) that the web app manifest should contain at least these keys: name
, short_name
, start_url
, and display
with a value of standalone
or fullscreen
; it also must have an icon at least 144×144
pixels large, in png
format.
Google also provides a more concrete list of requirements and how to both test and fix those requirements in your app in the Progressive Web Apps Checklist. This includes an additional list of key elements of "exemplary" PWAs, including factors like "when tapped, inputs aren't obscured by the on screen keyboard" and "Pressing back from a detail page retains scroll position on the previous list page."
So, should you drop all your plans for your current and future applications and try to make everything a PWA today?
Probably not.
Most web sites and web applications will function just fine in the majority of contexts without adding the complexity and cost of a PWA. That's not to say that they don't have a lot of benefit, or that we shouldn't be inspired by their increased accessibility... but don't just jump on this train because it's exciting and new.
However, it's absolutely worth learning about. This is the future. And even if you don't go full PWA, there's a lot to learn about the current and future state of progressive enhancement and offline access when you're learning about PWAs. Create-react-app
(the primary tool for creating new React apps) defaults to creating a PWA and they're getting easier and easier to create every day. But this is not a must.
The core technical requirements for any PWA application are service workers and a web app manifest. We'll also discuss the application shell architecture, push notifications, and IndexedDB. Please note that all PWAs must be served over HTTPS. Finally, much of what we're talking about here relies on relatively new browser APIs like fetch
, cache
, push
, and notifications
.
For starters, let's talk about the basic technical architecture of a PWA. Essentially, we're talking about a web application, or a web site that's likely using some JavaScript to provide content and (likely) an interactive experience to the end user. Nothing special, nothing native, nothing hybrid—just a web site delivering files over HTTP.
However, in the case of a PWA, this app will register service workers, which we'll cover in a second, which will then speed up the app, decrease its load time on future visits, and even keep the app working even when the user has no Internet connection. The service workers will serve assets to the user and capture user interactions, likely storing them in IndexedDB, and then sync the captured data back to the server once the user regains an Internet connection.
Additionally, PWAs use advanced technologies and careful structuring to load and respond quickly and interact with the user and the device in more ways than are possible for a normal web app.
Now, let's dig into the tools and technologies that make this possible.
Service workers are browser scripts (JavaScript workers) that run in the background of your application. The simplest way to think of them is as a programmable proxy that lives client-side and sits between your browser and the rest of the Internet.
A common usage of a service worker would be, once registered, to download a cache of important static assets ("pre-cache"), and then intercept requests from your browser and serve the cached assets instead of the actual assets. Additionally, it's common to teach a service worker a pattern to identify other outgoing requests for assets, cache each asset after it's returned by an HTTP query, and intercept future requests to that asset and return the cached version.
By pre-loading and caching certain assets, service workers can both make your application faster, and also make it function even without Internet access.
Service workers can also react to messages explicitly sent by your application. They don't have direct access to your page, but in tandem with some JavaScript they can provide some really powerful asynchronous and offline capabilities. For example, they can enable push notifications, background sync, and geolocation.
Service workers are short-lived, running only when requested and then powering back down. They use Promises to handle concurrency and often use IndexedDB to store data—so you may need to get familiar with both.
One final note: service workers run on a separate thread from your application and they can receive messages even when your application isn't focused, enabling push notifications and background sync and other usually-not-possible-on-the-web types of behavior.
Developers can register a service worker in an application using navigator.serviceworker.register
; this will load the service worker definition file and run its install
step, which will likely cache some files.
Service workers will usually also define a fetch
step, which can intercept any requests from the application, and a sync
step, which defines behavior the application should take when reconnecting to the Internet after having lost access.
I won't go into much detail here, but here's an idea of what we're working with:
// in your applicationwindow.addEventListener('load', function () { navigator.serviceWorker.register('/myworker.js').then(function (registration) { // Registration was successful }, function (err) { // Registration failed });}); // in myworker.js// cache files under the my-site-cache-v1 namespacevar CACHE_NAME = 'my-site-cache-v1';var urlsToCache = [ '/', '/styles/main.css', '/script/main.js']; self.addEventListener('install', function (event) { // Perform install steps — cache given URLs event.waitUntil( caches.open(CACHE_NAME) .then(function (cache) { console.log('Opened cache'); return cache.addAll(urlsToCache); }) );}); // define proxy to return cached files when app requests themself.addEventListener('fetch', function (event) { event.respondWith( caches.match(event.request) .then(function (response) { // Cache hit - return response if (response) { return response; } return fetch(event.request); } ) );});
(above scripts modified from Google Developers Service Workers primer)
To learn more about service workers, check out Google Developers' Service Workers primer, the HTML5Rocks page on JavaScript Workers, and the Google Developers Progressive Web Apps Training section on Service Workers.
A manifest is a JSON file associated with your application that allows you to define how your application can be "installed" to the home screen of a user's device. Manifests also make it possible to customize the ways your app will interact with your device's operating system outside of just existing in a browser window.
A manifest file lets the developer define whether your app can be launched full-screen, how various user interface elements will be shown or hidden or colored, which icons to use at various resolutions, and a few other key definitions that are necessary for a device to treat a web app as a real app.
As I mentioned before, web app manifests for PWAs have to have at least the following four properties:
name
short_name
start_url
display
with a value of standalone
or fullscreen
And they must have an icon at least 144×144
pixels large, in png
format.
Here's a sample web manifest:
{ "name": "My Application", "short_name": "my_application", "theme_color": "#208075", "background_color": "#208075", "start_url": "index.html", "display": "standalone", "orientation": "landscape", "icons": [ { "src": "launcher-icon-3x.png", "sizes": "144x144", "type": "image/png" }, { "src": "launcher-icon-4x.png", "sizes": "192x192", "type": "image/png" } ],}
To link this file to your application, save it on your server somewhere and place this in the head of your file:
<link rel="manifest" href="/manifest.json">
Remember: beyond service workers and the web app manifest, all remaining technologies are optional depending on the needs of your app.
IndexedDB is the tool you'll likely use to store user data when the application doesn't have access to the Internet (and, optionally, any other time you want). You can learn more about how to interact with IndexedDB in Google's Progressive Web Apps Training, in the "Working with IndexedDB" module, and we'll just cover the absolute basics here.
IndexedDB is an object store, which means it's not relational and doesn't use SQL. If you've used NoSQL databases like MongoDB, IndexedDB will feel pretty familiar. Essentially you're going to create one or more "object stores" (sort of like database tables) and fill them with objects (JSON).
The IndexedDB API itself is notoriously difficult to work with, so you'll want to pick a wrapper. Google recommends Jake Archibald's IndexedDB Promised Library, but Dexie seems to be even more popular.
Here's a quick example of just two operations you can perform with IndexedDB, using Dexie's syntax:
// Declare Databasevar db = new Dexie("FriendDatabase");db.version(1).stores({ friends: "++id,name,age"}); // Manipulate and Query Databasedb.friends.add({name: "Josephine", age: 21}).then(function() { return db.friends.where("age").below(25).toArray();}).then(function (youngFriends) { alert ("My young friends: " + JSON.stringify(youngFriends));}).catch(function (e) { alert ("Error: " + (e.stack || e));});
When you start reading up about PWAs you'll often hear about the application shell architecture, load time, and headroom. This comes from the fact that the PWA definition focuses heavily on load time for slower devices—both slow Internet and slow processors.
The app shell architecture is the idea that you will separate out the HTML, CSS, and JavaScript needed to present the "shell" of your application—the primary UI elements and layout structures—so that its delivery can be prioritized, optimized, and cached.
Essentially, you'll want to create a chunk of code that is everything but the content, deliver and display that first, and then cache it heavily using service workers so that future loads of your page feel much faster. This means you can get your basic layout and UI on the page fast without any network requests, and you can even get it on the page without network access at all.
Read more about application shell architecture in Google's Web Fundamentals series and on Google Developers' Medium, both from Addy Osmani.
You can also take a look at Jake Archibald's Wikipedia PWA, which uses the application shell architecture.
During the time I was writing this post, Addy Osmani took the stage at Google I/O and referenced a dozen new technologies for me to write about. Most of what I've written here was starting to get solidified at the end of 2016, but progress on these technologies has obviously continued.
Here's what is most important: you can still use PWAs without any of this new fanciness. Most of the content Addy covered is adding new—and helpful!—capabilities for people who already get the basics and need to optimize further. If you are already feeling overwhelmed by this post, you can skip this section for now; you'll be just fine.
Sarah Drasner from CSS Tricks was at I/O and wrote up some of the basics in her post Production Progressive Web Apps with Javascript Frameworks.
A few of the technologies Addy mentioned at I/O aren't new at all; it's worth learning about link rel="preload"
(quick writeup) for instructing the browser to preload assets, and link rel="dns-prefetch"
(quick writeup) for instructing the browser to pre-resolve the DNS for a given domain before it's actually called.
Addy also covered HTTP/2 ("H2", as he says it) Server Push, which you can learn about at Smashing Magazine's HTTP/2 Server Push writeup; server push allows the server to "push" assets to the client without the user's browser specifically requesting them. He also mentioned requestIdleCallback()
, which makes it possible to instruct the browser to defer non-critical processing to when the user is idle or when there is free time at the end of a render frame; you can learn more at this Google Developers' article on requestIdleCallback.
He also described the PRPL pattern, which is so new I wouldn't even worry about it. It's so new that it's even new in Addy's world, which means normal people like us shouldn't have to worry about it for months to years, if ever. In essence it is prioritizing what you're going to use first, by Pushing critical resources for the initial URL route, then Rendering the initial route, Pre-caching remaining routes, and Lazy-loading and creating the remaining routes on demand. PRPL.
One point that comes up often in developing PWAs is that the "time to interactivity" (TTI)—the time between page load and when the user can interact with your page—is vital. Google's PWA checklist requires a less-than-10-second TTI on "an average device" (Nexus 5 or equivalent) on a 3G network, which has repercussions both on the file size of the initial payloads and also the processing cost of the scripts.
In Addy's talk he referenced that they're shooting for a five-second TTI. I reached out to him on Twitter and he pointed out the RAIL performance model, and he described how they prescribe "a TTI of under 5000ms on average hardware over 3G." This number is based on quite a bit of research and practical experience, but one key factor was Google's research that "53% of mobile users will abandon a site if it doesn't load in 3s".
Recognizing that most PWAs will be built on a framework, you can now easily test how each framework enables you—or doesn't—to get to that five second mark. And that's when we start talking about headroom. Again, if I've understood this all correctly, "headroom" means something along the lines of "how many of those five seconds of your interactivity budget are not consumed by loading your framework." How much time do you get to load and initialize the scripts you've written yourself?
So, if you're trying to find a JavaScript framework that is going to give you headroom on medium-power mobile devices on slower connections, what are your options? Turns out that React and Angular will take quite a bit of work to get under 5s TTI right now, although both are improving. If you want to shoot for maximum headroom, you'll want to instead turn to Vue, Svelte, or Preact, which is a React alternative with almost exactly the same API but a much smaller footprint.
If you aren't looking to change your entire framework just for considering PWAs, here's my recommendation: either use Vue, or use React (Create-React-App now creates PWAs out of the box) and consider Preact for certain contexts.
Finally, Addy, who was one of the original creators of TodoMVC (which a generation of frontend devs used to compare JavaScript frameworks) has now worked with a team of developers to create HNPWA: Hacker News readers as Progressive Web Apps. You can see how a single app—a Hacker News client—looks across a variety of tech stacks, and compare useful information like their time to interactive on 3G and emerging markets, their Lighthouse score, and more.
We'll cover creating your own PWA, including setting up service workers and offline access, app shells, Vue and React and Preact, and more in the next blog post in this series. Sign up to get emails with our new blog posts or follow us on Twitter so you can catch the next post when it's live.
Here are a few tools and reference document I used in creating this post and/or would use in writing PWAs.
These are a few other resources I used and/or would recommend in understanding PWAs better. As you can probably tell, Google is (unsurprisingly) the golden source for all things PWA.
We appreciate your interest.
We will get right back to you.