Progressive Web Apps use modern web capabilities to deliver an app-like user experience.
They evolve from pages in browser tabs to immersive, top-level apps, maintaining the web's low friction at every moment.
So they're just web pages?
Works for every user, regardless of browser choice because it's built with progressive enhancement as a core tenet.
Fits any form factor: desktop, mobile, tablet, or whatever is next.
Enhanced with service workers to work offline or on low-quality networks.
Feels like an app, because the app shell model separates the application functionality from application content.
Shell
Content
Served via HTTPS to prevent snooping and to ensure content hasn't been tampered with
Makes re-engagement easy through features like push notifications.
Allows users to add apps they find most useful to their home screen without the hassle of an app store.
Easily share the application via URL, does not require complex installation.
Is identifiable as an "application" thanks to W3C manifest and service worker registration scope, allowing search engines to find it.
// index.html
<link rel="manifest" href="/manifest.json">
// manifest.json
{
"short_name": "AirHorner",
"name": "Kinlan's AirHorner of Infamy",
"icons": [
{
"src": "launcher-icon-1x.png",
"type": "image/png",
"sizes": "48x48"
},
{
"src": "launcher-icon-2x.png",
"type": "image/png",
"sizes": "96x96"
},
{
"src": "launcher-icon-4x.png",
"type": "image/png",
"sizes": "192x192"
}
],
"start_url": "index.html?launcher=true"
}
{
"short_name": "PWA Test",
"name": "PWA Test",
"start_url": "index.html",
"display": "standalone",
"background_color": "#fff",
"theme_color": "#2196F3",
"description": "A sample PWA.",
"icons": [
{
"src": "img/logo-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "img/logo-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
{
"short_name": "PWA Test",
"name": "PWA Test",
"start_url": "index.html",
"display": "standalone",
"background_color": "#fff",
"theme_color": "#2196F3",
"description": "A sample PWA.",
"icons": [
{
"src": "img/logo-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "img/logo-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
{
"short_name": "PWA Test",
"name": "PWA Test",
"start_url": "index.html",
"display": "standalone",
"background_color": "#fff",
"theme_color": "#2196F3",
"description": "A sample PWA.",
"icons": [
{
"src": "img/logo-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "img/logo-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
{
"short_name": "PWA Test",
"name": "PWA Test",
"start_url": "index.html",
"display": "standalone",
"background_color": "#fff",
"theme_color": "#2196F3",
"description": "A sample PWA.",
"icons": [
{
"src": "img/logo-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "img/logo-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
{
"short_name": "PWA Test",
"name": "PWA Test",
"start_url": "index.html?start=a2hs",
"display": "standalone",
"background_color": "#fff",
"theme_color": "#2196F3",
"description": "A sample PWA.",
"icons": [
{
"src": "img/logo-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "img/logo-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
{
"short_name": "PWA Test",
"name": "PWA Test",
"start_url": "index.html",
"display": "standalone",
"background_color": "#fff",
"theme_color": "#2196F3",
"description": "A sample PWA.",
"icons": [
{
"src": "img/logo-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "img/logo-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
{
"short_name": "PWA Test",
"name": "PWA Test",
"start_url": "index.html",
"display": "standalone",
"background_color": "#fff",
"theme_color": "#2196F3",
"description": "A sample PWA.",
"icons": [
{
"src": "img/logo-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "img/logo-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
{
"short_name": "PWA Test",
"name": "PWA Test",
"start_url": "index.html",
"display": "standalone",
"background_color": "#fff",
"theme_color": "#2196F3",
"description": "A sample PWA.",
"icons": [
{
"src": "img/logo-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "img/logo-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
{
"short_name": "PWA Test",
"name": "PWA Test",
"start_url": "index.html",
"display": "standalone",
"background_color": "#fff",
"theme_color": "#2196F3",
"description": "A sample PWA.",
"icons": [
{
"src": "img/logo-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "img/logo-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
A service worker is a script that your browser runs in the background, separate from a web page
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/service-worker.js');
});
}
var CACHE_VERSION = '%CACHE_VERSION%';
var CACHE_LIST = ['CACHE_VALUES'];
self.addEventListener('install', function(event) {
event.waitUntil(caches.open(CACHE_VERSION)
.then(function(cache) {
return cache.addAll(CACHE_LIST);
}));
});
self.addEventListener('activate', function(event) {
console.log("service worker has been activated.");
});
self.addEventListener('fetch', function(event) {
console.log('Handling fetch event for ' + event.request.url);
event.respondWith(
caches.match(event.request).then(function(response) {
if (response) {
console.log('Found response in cache:', response);
return response;
}
console.log('No response found in cache. Fetch from network...');
return fetch(event.request);
})
);
});
{
"short_name": "PWA Test",
"name": "PWA Test",
"start_url": "index.html",
"display": "standalone",
"background_color": "#fff",
"theme_color": "#2196F3",
"description": "A sample PWA.",
"icons": [
{
"src": "img/logo-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "img/logo-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
<meta
name="apple-mobile-web-app-title"
content="PWA Test">
{
"short_name": "PWA Test",
"name": "PWA Test",
"start_url": "index.html",
"display": "standalone",
"background_color": "#fff",
"theme_color": "#2196F3",
"description": "A sample PWA.",
"icons": [
{
"src": "img/logo-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "img/logo-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
<meta
name="apple-mobile-web-app-capable"
content="yes">
{
"short_name": "PWA Test",
"name": "PWA Test",
"start_url": "index.html",
"display": "standalone",
"background_color": "#fff",
"theme_color": "#2196F3",
"description": "A sample PWA.",
"icons": [
{
"src": "img/logo-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "img/logo-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
<meta name=
"apple-mobile-web-app-status-bar-style"
content="black">
{
"short_name": "PWA Test",
"name": "PWA Test",
"start_url": "index.html",
"display": "standalone",
"background_color": "#fff",
"theme_color": "#2196F3",
"description": "A sample PWA.",
"icons": [
{
"src": "img/logo-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "img/logo-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
<link
rel="apple-touch-icon"
href="img/icons/apple-touch-icon.png">
{
"short_name": "PWA Test",
"name": "PWA Test",
"start_url": "index.html",
"display": "standalone",
"background_color": "#fff",
"theme_color": "#2196F3",
"description": "A sample PWA.",
"icons": [
{
"src": "img/logo-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "img/logo-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
<link
rel="apple-touch-startup-image"
href="img/Default-Portrait.png">
cordova plugin add https://github.com/phonegap/phonegap-plugin-service-worker
<preference name="ServiceWorker" value="service-worker.js" />
<preference name="CacheCordovaAssets" value="false" />
cordova create myApp --template=<PATH TO ASSETS>
Create an App from your assets
Add the Service Worker plugin
Modify config.xml
cordova run ios
Run your app
npm install -g cordova
Install the Cordova CLI