Progressive Web Apps — An Introduction
July 4, 2018
Thomas Steiner
Developer Advocate
Google Hamburg
@tomayac
Confidential + Proprietary
Confidential + Proprietary
Progressive Web Apps — An Introduction to Some of the Concepts of PWA
July 4, 2018
Thomas Steiner
Developer Advocate
Google Hamburg
@tomayac
Confidential + Proprietary
Confidential + Proprietary
Source: comScore
Global web users (millions)
vs.
Source: comScore Mobile Metrix, U.S., Age 18+, June 2017
13%
87%
Mobile web
Apps
78%
Source: comScore Mobile Metrix (Custom), U.S., Age 18+, June 2017
of time spent is in�users’ top 3 apps
ZERO
Source: comScore MobiLens, U.S., Age 13+, 3 Month Average Ending June 2017
Number of apps the average user installs per month
So, what exactly is a progressive web app?
Fancy name for "Modern Website" that takes advantage of new APIs and builds experiences users expect
Reliable
Fast
Engaging
Confidential + Proprietary
Add to Homescreen
Make it easy for users to come back
Trivago: engagement for users who add to homescreen has increased by 150%
Push Notifications
Re-engage users
Offline
Never see the dinosaur again
Enable reliable experiences with the service worker
Web server
The old way...
Confidential + Proprietary
Enable reliable experiences with the service worker
Web server
The old way...
Confidential + Proprietary
Enable reliable experiences with the service worker
Web server
Service worker
Cache API
Client Device
… the new way
Confidential + Proprietary
Advanced caching strategies with service worker
“Cache then network”
Confidential + Proprietary
App Shell Model
Keep your app reactive, predictable and in control
Avoid overly “web-like” design
Sites aren’t reactive�
Sites aren’t predictable
The user is not in control�
Confidential + Proprietary
App Shell Model
Keep your app reactive, predictable and in control
Benefits
Confidential + Proprietary
App Shell Model
Keep your app reactive, predictable and in control
App shell model
Content placeholders
No shifting content
Confidential + Proprietary
App Shell Model
Keep your app reactive, predictable and in control
Download only what’s needed with PWAs
Android App
30 MB+
0.6 MB
iOS App
PWA
100 MB+
Confidential + Proprietary
Fast
Engaging
Reliable
Integrated
Fast
Cache, fall back to network
4:00pm
Web server
Service worker
Local Cache
Client Device
1
Confidential + Proprietary
Cache, fall back to network
4:00pm
Web server
Service worker
Local Cache
Client Device
1
2
Confidential + Proprietary
Cache, fall back to network
4:00pm
Web server
Service worker
Local Cache
Client Device
1
2
3
Confidential + Proprietary
Cache, fall back to network
4:00pm
Web server
Service worker
Local Cache
Client Device
1
2
Confidential + Proprietary
Cache, fall back to network
4:00pm
Web server
Service worker
Local Cache
Client Device
1
2
3
Confidential + Proprietary
Cache, fall back to network
4:00pm
Web server
Service worker
Local Cache
Client Device
1
2
3
4
Confidential + Proprietary
Cache, then network
4:00pm
Web server
Service worker
Client Device
Leaderboard
Local Cache
Last update: 3:00pm
Confidential + Proprietary
Cache, then network
4:00pm
Web server
Service worker
Client Device
1
1
Leaderboard
Local Cache
Last update: 3:00pm
Confidential + Proprietary
Cache, then network
4:00pm
Web server
Service worker
Local Cache
Client Device
1
1
2
2
Local Cache
Last update: 3:00pm
Leaderboard
Last updated: 3:00 pm
Joe 2000
Sally 3000
Mark 4000
Confidential + Proprietary
Cache, then network
4:00pm
Web server
Service worker
Local Cache
Last update: 4:00pm
Client Device
1
1
2
2
3
3
Leaderboard
Last updated: 4:00 pm
Sally 5000
Joe 2000
Mark 4000
Confidential + Proprietary
Integrated
Add to�Home Screen
80%
Source: comScore Custom Survey, U.S., Age 18+, 2017 Wave
intentionally moved apps to their home screen
Broken Experience
Required user interaction�Buried deep in menus
Where would it start?�Dependent on bookmark
Would it work offline?�Users didn't expect offline
Web App Manifest
<link rel="manifest" href="/manifest.json">
<link rel="manifest" href="/manifest.json">
{
"name": "CNET Tech Today",
"short_name": "Tech Today",
"icons": [{
"src": "icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}, {...}],
"start_url": "/index.html",
"display": "standalone",
"orientation": "portrait",
"background_color": "#FF0000",
"theme_color": "#FF0000"
}
Home Screen Name & Icons
{
"name": "CNET Tech Today",
"short_name": "Tech Today",
"icons": [{
"src": "icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}, {...}],
"start_url": "/index.html",
"display": "standalone",
"orientation": "portrait",
"background_color": "#FF0000",
"theme_color": "#FF0000"
}
Splash Screen
{
"name": "CNET Tech Today",
"short_name": "Tech Today",
"icons": [{
"src": "icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}, {...}],
"start_url": "/index.html",
"display": "standalone",
"orientation": "portrait",
"background_color": "#FF0000",
"theme_color": "#FF0000"
}
Start Up Options
{
"name": "CNET Tech Today",
"short_name": "Tech Today",
"icons": [{
"src": "icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}, {...}],
"start_url": "/index.html",
"display": "standalone",
"orientation": "portrait",
"background_color": "#FF0000",
"theme_color": "#FF0000"
}
Task Switcher Colors
{
"name": "CNET Tech Today",
"short_name": "Tech Today",
"icons": [{
"src": "icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}, {...}],
"start_url": "/index.html",
"display": "standalone",
"orientation": "portrait",
"background_color": "#FF0000",
"theme_color": "#FF0000"
}
Good, but what about
Prompt to Install?
Prompt to Install
Prompt to Install
Prompt to Install
Prompt to Install
Baseline PWA
Exemplary PWA
Indexability & Social
User Experience
Performance
Caching
Push Notifications
Additional Features
Why are the requirements
so strict?
A Promise to the User
Fast
Engaging
Reliable
Integrated
Service Workers are a client-side proxy
Service worker
(written in JavaScript)
Cache
Web server
Service Worker Lifecycle
Activated
Error
Idle
Active
Terminated
Install
Register
Service Worker Lifecycle
Activated
Error
Idle
Active
Terminated
Install
Register
Service Worker Lifecycle
Activated
Error
Idle
Active
Terminated
Install
Register
Service Worker
is for the second load.
Lifecycle of a Service Worker
Installing the Service Worker
1st Page Load
install
idle
Lifecycle of a Service Worker
Installing the Service Worker
2nd Page Load
activated
check for update
Lifecycle of a Service Worker
Installing the Service Worker
Close Page
idle
terminated
activated
Lifecycle of a Service Worker
Updating a Service Worker
Load Page with Service Worker
activated
check for update
Lifecycle of a Service Worker
Updating a Service Worker
Load Page with Service Worker
activated
check for update
install
idle
Lifecycle of a Service Worker
Updating a Service Worker
Load Page with Service Worker
activated
check for update
install
idle
Implementing a Simple Service Worker
Register the Service Worker
Activated
Error
Idle
Active
Terminated
Install
Register
Register the Service Worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(function(reg) {
console.log('Service Worker Registered', reg);
})
.catch(function(err) {
console.log('Error registering Service Worker', err);
});
}
Register the Service Worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(function(reg) {
console.log('Service Worker Registered', reg);
})
.catch(function(err) {
console.log('Error registering Service Worker', err);
});
}
Add an install Event Handler
Activated
Error
Idle
Active
Terminated
Install
Register
Add an install Event Handler
self.addEventListener('install', function(event) {
return self.skipWaiting();
});
Pre-fetch the App Resources
Activated
Error
Idle
Active
Terminated
Install
Register
Pre-fetch the App Resources
var cacheName = 'app-shell-cache-v1';
var filesToCache = ['/', '/index.html', ...];
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(cacheName).then(function(cache) {
return cache.addAll(filesToCache);
}).then(function() {
return self.skipWaiting();
})
);
});
Pre-fetch the App Resources
var cacheName = 'app-shell-cache-v1';
var filesToCache = ['/', '/index.html', ...];
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(cacheName).then(function(cache) {
return cache.addAll(filesToCache);
}).then(function() {
return self.skipWaiting();
})
);
});
Pre-fetch the App Resources
var cacheName = 'app-shell-cache-v1';
var filesToCache = ['/', '/index.html', ...];
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(cacheName).then(function(cache) {
return cache.addAll(filesToCache);
}).then(function() {
return self.skipWaiting();
})
);
});
Pre-fetch the App Resources
var cacheName = 'app-shell-cache-v1';
var filesToCache = ['/', '/index.html', ...];
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(cacheName).then(function(cache) {
return cache.addAll(filesToCache);
}).then(function() {
return self.skipWaiting();
})
);
});
Add an activate Event Handler
Activated
Error
Idle
Active
Terminated
Register
Install
Add an activate Event Handler
self.addEventListener('activate', function(e) {
e.waitUntil(
caches.keys().then(function(keyList) {
return Promise.all(keyList.map(function(key) {
if (key !== cacheName) {
return caches.delete(key);
}
}));
})
);
return self.clients.claim();
});
Add an activate Event Handler
self.addEventListener('activate', function(e) {
e.waitUntil(
caches.keys().then(function(keyList) {
return Promise.all(keyList.map(function(key) {
if (key !== cacheName) {
return caches.delete(key);
}
}));
})
);
return self.clients.claim();
});
Not Done Yet…
Activated
Error
Idle
Active
Terminated
Install
Register
Add a fetch Event Handler
self.addEventListener('fetch', function(e) {
e.respondWith(
caches.match(e.request).then(function(response) {
return response || fetch(e.request);
})
);
});
Add a fetch Event Handler
self.addEventListener('fetch', function(e) {
e.respondWith(
caches.match(e.request).then(function(response) {
return response || fetch(e.request);
})
);
});
Add a fetch Event Handler
self.addEventListener('fetch', function(e) {
e.respondWith(
caches.match(e.request).then(function(response) {
return response || fetch(e.request);
})
);
});
Add a fetch Event Handler
self.addEventListener('fetch', function(e) {
e.respondWith(
caches.match(e.request).then(function(response) {
return response || fetch(e.request);
})
);
});
Ready to Go!
Activated
Error
Idle
Active
Terminated
Install
Register
Scope
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(function(reg) {
console.log('Service Worker Registered', reg);
})
.catch(function(err) {
console.log('Error registering Service Worker', err);
});
}
Scope
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(function(reg) {
console.log('Service Worker Registered', reg);
})
.catch(function(err) {
console.log('Error registering Service Worker', err);
});
}
Precaching static resources
Precaching (SW)
Precaching (Workbox)
Runtime caching (Workbox)
Runtime caching (SW)
Basic offline functionality.
Implements different cache strategies for different kinds of assets.
Need to deal with navigation and non-navigation fetch events.
Q & A
Thomas Steiner
Developer Advocate
Google Hamburg
@tomayac