๐ฑ PWA Architecture and Capabilities
A Progressive Web App is an ordinary website that has learned a few new tricks: it can be installed to the home screen, launch full-screen, work offline, and re-engage users with notifications โ all from the same URL you already ship. This lesson maps the moving parts so the two lessons that follow (service workers and offline-first) fit into a clear whole.
๐ฏ Learning Objectives
By the end of this lesson, you will be able to:
- Explain what "progressive" means and how progressive enhancement underpins PWAs
- Identify the three technical pillars โ HTTPS, the web app manifest, and service workers
- Author a valid
manifest.webmanifestand wire up an install prompt - Describe the app-shell and PRPL architecture patterns and when to use them
- Audit a real PWA with Lighthouse and read the installability report
Estimated Time: 35โ45 minutes โข Difficulty: Intermediate
Hands-on: Add a manifest and a custom install button to a plain web page, then verify it in Chrome DevTools.
In This Lesson
What Is a Progressive Web App?
A Progressive Web App (PWA) is a website built with a set of browser capabilities that let it feel like a platform app: it can be installed, run offline, and behave reliably on flaky networks. There is no PWA framework to install and no app store to submit to โ a PWA is a capability profile, not a new kind of project. Any React, Angular, Vue, or hand-written site can become one.
The word progressive comes from progressive enhancement: you build a baseline that works everywhere, then layer on advanced features for browsers that support them. A browser that lacks service workers still gets a working website; a modern one additionally gets offline support and installability.
๐ก A useful analogy: A PWA is like a hybrid car. On the open highway (a modern browser) it uses every system it has. When conditions get worse (an old browser, a dead network) it quietly falls back to the essentials and still gets you where you're going โ it never simply stops.
๐ Key Terms
Progressive enhancement: start from a functional baseline and add capabilities only where they're supported.
Installability: the browser's ability to add your app to the home screen or app launcher.
App shell: the minimal HTML/CSS/JS that renders your UI frame, cached for instant repeat loads.
The term was coined in 2015 by Alex Russell and Frances Berriman to name a pattern that was already emerging. Since then, companies from Twitter to Starbucks have shipped PWAs to cut load times and data usage while maintaining a single codebase for web and "app."
The Three Technical Pillars
Three requirements turn a site into an installable, offline-capable PWA. Miss any one and the browser will not offer to install it.
1. HTTPS โ a secure origin
Service workers can intercept every request your page makes, so the browser only allows them on secure origins. In practice that means HTTPS in production (and http://localhost during development, which the browser treats as secure). HTTPS also protects the integrity of the code the browser caches.
2. The web app manifest
A small JSON file that tells the browser what to display when the app is installed โ its name, icons, colors, and how it should launch. Without it, there is nothing to install. We cover it in detail next.
3. The service worker
A background JavaScript file that acts as a programmable network proxy. It can serve cached responses when the network is down, sync data later, and receive push messages. This is the topic of the next lesson.
โ ๏ธ A manifest alone is not offline
A manifest makes an app installable, but an installed PWA with no service worker still shows the browser's offline error when the network drops. Offline reliability comes from the service worker's caching โ the manifest just controls how the app looks and launches.
The Web App Manifest
The manifest is a JSON file (conventionally manifest.webmanifest, though .json also works) linked from every page:
<link rel="manifest" href="/manifest.webmanifest">
<meta name="theme-color" content="#2f3ba2">
A minimal but installable manifest needs a name, a start URL, a standalone display mode, and at least a 192px and a 512px icon (a maskable icon is strongly recommended so Android can crop it to the device's icon shape):
{
"name": "Weather Now",
"short_name": "Weather",
"description": "Fast, offline-capable weather forecasts",
"start_url": "/?source=pwa",
"scope": "/",
"display": "standalone",
"background_color": "#3e4eb8",
"theme_color": "#2f3ba2",
"icons": [
{ "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png" },
{
"src": "/icons/icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}
๐ Key manifest fields
display โ standalone (no browser chrome, most common), fullscreen, minimal-ui, or browser.
start_url โ where the app opens from the home-screen icon; add a query param to measure launches in analytics.
scope โ the set of URLs the installed app "owns"; navigations outside it open in a browser tab.
theme_color โ tints the OS status bar and task-switcher card.
You do not have to hand-write eight icon sizes anymore. Modern builds generate them from a single source: Vite PWA and the Angular and CLI generators emit the manifest and icons for you. What matters is understanding what those fields do.
Installability & the Install Prompt
When a site meets the install criteria (HTTPS, a valid manifest, and โ on Chromium โ a registered service worker with a fetch handler), the browser fires a beforeinstallprompt event. Capturing it lets you show your own install button at a moment that makes sense, instead of the browser's default mini-banner.
let deferredPrompt = null;
const installButton = document.querySelector('#install-btn');
// The browser wants to show its install prompt โ intercept it.
window.addEventListener('beforeinstallprompt', (event) => {
event.preventDefault(); // suppress the automatic mini-infobar
deferredPrompt = event; // stash it for later
installButton.hidden = false; // reveal our own button
});
installButton.addEventListener('click', async () => {
if (!deferredPrompt) return;
deferredPrompt.prompt(); // show the native dialog
const { outcome } = await deferredPrompt.userChoice;
console.log(`User response: ${outcome}`); // 'accepted' | 'dismissed'
deferredPrompt = null; // it can only be used once
installButton.hidden = true;
});
// Fires after a successful install (from your button OR the browser menu).
window.addEventListener('appinstalled', () => {
installButton.hidden = true;
console.log('PWA installed');
});
Console output after the user accepts
User response: accepted
PWA installed
โ ๏ธ iOS is different
Safari on iOS does not fire beforeinstallprompt. Users install a PWA manually via the Share menu โ "Add to Home Screen." Detect installed/standalone mode with window.matchMedia('(display-mode: standalone)').matches and consider showing iOS users a short hint instead of an install button.
Capabilities Beyond the Browser
Once installed and backed by a service worker, a PWA unlocks capabilities that used to require a native app.
| Capability | What it enables | Powered by |
|---|---|---|
| Offline access | App loads and functions with no network | Service worker + Cache API |
| Push notifications | Re-engage users even when the app is closed | Push API + service worker |
| Background sync | Defer failed writes until connectivity returns | Background Sync API + IndexedDB |
| Installability | Home-screen icon, standalone window | Manifest |
| Discoverability | Indexed by search engines, shareable via URL | It is still the web |
๐ก Why teams choose PWAs
Starbucks rebuilt its ordering flow as a PWA roughly 100ร smaller than its native app while still working offline to browse the menu. Uber's PWA loads in seconds on 2G. The recurring win: one codebase, no app-store gatekeeping, and reach on devices where users will never install a native app.
Architecture Patterns
The app-shell model
Split your app into a shell โ the header, navigation, and layout that rarely change โ and the content that fills it. Cache the shell on first visit so subsequent loads paint instantly from the cache while fresh content streams in over the network.
The PRPL pattern
PRPL is a performance-oriented delivery strategy. Modern bundlers automate most of it, but knowing the intent helps you configure them:
- Push / preload the critical resources for the first route
- Render that initial route as fast as possible
- Pre-cache the remaining routes in the service worker
- Lazy-load everything else on demand via code-splitting
โ Auditing with Lighthouse
Open Chrome DevTools โ Lighthouse, tick the "Progressive Web App" and "Performance" categories, and generate a report. It checks the manifest, HTTPS, offline start, icon sizes, and viewport, and gives concrete fixes. Run it after every meaningful change โ it is the fastest feedback loop for PWA compliance.
Hands-on Exercise
๐๏ธ Make a plain page installable
Objective: Add a manifest and a working install button to an existing static page, then verify installability in DevTools.
Instructions:
- Create
manifest.webmanifestwithname,short_name,start_url,display: "standalone",theme_color, and a 192px + 512px icon. - Link it from your
<head>and add a<meta name="theme-color">. - Register a minimal service worker (just an empty
fetchhandler is enough to satisfy the install criteria) โ you'll flesh it out in the next lesson. - Add a hidden
#install-btnand wire up thebeforeinstallprompthandler shown above. - Serve over
localhost, open DevTools โ Application โ Manifest, and confirm there are no errors and the "Install" affordance appears.
๐ก Hint
If the install button never appears, check the Application โ Manifest panel for warnings (a missing 512px icon and a non-standalone display are the usual culprits), and confirm your service worker registered without errors in Application โ Service Workers. Remember it will not fire on file:// โ you must serve the page.
โ Minimal service worker to satisfy the criteria
// sw.js โ the smallest SW that makes Chrome offer installation
self.addEventListener('install', () => self.skipWaiting());
self.addEventListener('activate', (e) => e.waitUntil(clients.claim()));
self.addEventListener('fetch', (event) => {
// A fetch handler must exist for installability; pass through for now.
event.respondWith(fetch(event.request));
});
Register it from your page: navigator.serviceWorker?.register('/sw.js');
๐ฏ Quick Quiz
Question 1: Which file makes a PWA installable by describing its name, icons, and display mode?
Question 2: Why do service workers require HTTPS (or localhost)?
Question 3: What does the app-shell pattern optimize for?
Best Practices
โ Do
- Ship a baseline that works before the service worker installs (progressive enhancement).
- Provide a maskable 512px icon and a sensible
theme_color. - Show the install prompt in response to a user gesture at a relevant moment, not on first paint.
- Run Lighthouse after every meaningful change and fix its PWA warnings.
โ ๏ธ Don't
- Don't assume a manifest gives you offline support โ that requires a caching service worker.
- Don't cache authenticated or user-specific responses without careful thought.
- Don't rely on
beforeinstallprompton iOS โ it never fires there. - Don't nag: an install prompt shoved at every visitor hurts trust and conversion.
Summary & Quiz
๐ Key Takeaways
- A PWA is a capability profile layered onto a normal website via progressive enhancement โ not a framework.
- The three pillars are HTTPS, the web app manifest, and a service worker.
- The manifest controls installability and appearance; the service worker controls offline behavior.
- Capture
beforeinstallpromptto offer a tasteful, well-timed install experience (iOS excepted). - The app-shell and PRPL patterns keep first and repeat loads fast.
๐ Further Reading
- web.dev โ Progressive Web Apps
- MDN โ Progressive web apps
- web.dev โ Add a web app manifest
- Chrome โ Lighthouse overview
๐ What's Next?
You now know what a PWA is and which pieces make it installable. Next we go deep on the engine behind offline behavior โ the service worker: its lifecycle, how it intercepts fetches, and the caching strategies you'll reach for.
๐ Nice work!
The map is in place. Time to build the engine room โ service workers await.