PWA (Progressive Web App) is a web application that uses modern browser capabilities to work like a native app: it loads quickly, works offline, sends push notifications and can be installed on the home screen. PWA is built on three technologies: Service Worker, manifest and HTTPS. Service Worker is a script that the browser runs in the background, separately from the web page, enabling capabilities unavailable to regular websites: intercepting network requests, caching resources, background synchronization and push notifications. The manifest is a JSON file that tells the browser how the application should look when installed on a device: name, icons, theme color, screen orientation. Without HTTPS, Service Worker does not work — this is a mandatory security requirement. PWAs are compatible with any modern browser and do not require publication in an app store. The user opens the site, sees an offer to install the application, taps "Add to Home Screen" — and the app is ready. For businesses, PWA means reaching any audience without separate development costs for iOS and Android, fast loading even on slow networks, and engagement through push notifications. In this article, we will explore how Service Worker works, how to create a manifest, which caching strategies to use and how to implement offline access.
Key Takeaways
Key Takeaways
Progressive Web App (PWA) is an approach to web application development that combines the best qualities of the web and native apps. The term was coined by Francesco Berriman in 2015, and Google actively promotes PWA as a replacement for simple mobile applications. The main idea: a website progressively improves depending on browser and device capabilities. If the browser supports Service Worker — offline mode is enabled. If it supports the manifest — an "Install" button appears. If the notifications API is available — the app can send push messages. A user on a weak device with a slow internet connection gets the basic version of the site, while on a modern smartphone they get a full-fledged app with an icon on the desktop.
PWAs do not need to be installed through an app store. A link is enough. This solves the main problem of mobile apps — the high entry barrier. The user does not go to Google Play or the App Store, does not wait for a 100 MB download, does not grant a dozen permissions. They simply open the site, and the browser offers to install the app. If the user declines — they still use the site. And if they agree — the icon appears on the home screen, and the app opens like a native one: without the browser address bar, with fullscreen mode and fast launch.
Technically, a PWA must meet three criteria: work over HTTPS (secure connection), have a Service Worker (background processing script) and include a manifest (Web App Manifest). These three components turn a regular website into an installable application.
Service Worker is a JavaScript script that the browser runs in a separate thread, independent of the web page. It does not have access to the DOM, cannot directly interact with the interface, but it can intercept and modify network requests, manage the cache, handle push notifications and perform background synchronization. Service Worker acts as a proxy server between the browser and the network: every request from the page goes through the Service Worker, which decides — to serve data from the cache, request from the server, or do both.
Service Worker has its own lifecycle, not tied to the open page. It installs, activates and runs in the background even after all site tabs are closed. This allows PWAs to receive push notifications and update the cache in the background, without user involvement.
Service Worker goes through four stages: registration, installation, activation and idle/termination.
Registration. The browser receives a link to the Service Worker JS file and begins downloading. Registration is done from regular JavaScript on the page:
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('SW registered:', registration.scope);
})
.catch(error => {
console.log('SW registration failed:', error);
});
}Installation. After successful download, the browser fires the install event. At this stage, it is common to cache key resources (HTML shell, CSS, main images) so the app can work offline. If the installation succeeds, the Service Worker moves to the next stage. If the script throws an error — installation is cancelled, and the old Service Worker continues to work.
self.addEventListener('install', event => {
event.waitUntil(
caches.open('pwa-cache-v1').then(cache => {
return cache.addAll([
'/',
'/index.html',
'/styles/main.css',
'/scripts/app.js',
'/images/logo.png'
]);
})
);
});Activation. When all site tabs are closed, the old Service Worker terminates and the new one activates. The activate event is used to clean up old caches and migrate data:
self.addEventListener('activate', event => {
const cacheWhitelist = ['pwa-cache-v2'];
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (!cacheWhitelist.includes(cacheName)) {
return caches.delete(cacheName);
}
})
);
})
);
});Idle. An activated Service Worker intercepts requests and handles events. It may be stopped by the browser to save resources and restarted on the next request. Service Worker is event-driven: it lives only while processing events.
Service Worker supports several caching strategies. The choice depends on the resource type and data freshness requirements.
Cache First. The cache is checked first. If the resource is found — it is returned from cache. If not — the request goes to the server, the response is saved to cache. This strategy is suitable for static resources: images, fonts, CSS, JS. It provides maximum speed and full offline functionality.
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(response => {
return response || fetch(event.request).then(fetchResponse => {
return caches.open('pwa-cache-v1').then(cache => {
cache.put(event.request, fetchResponse.clone());
return fetchResponse;
});
});
})
);
});Network First. The request goes to the server first. If the server responds — data is returned to the user and saved to cache. If the server is unavailable — the cached copy is served. This strategy is suitable for API requests, news, content that must be up-to-date but can also work offline.
self.addEventListener('fetch', event => {
event.respondWith(
fetch(event.request).then(response => {
return caches.open('pwa-dynamic-v1').then(cache => {
cache.put(event.request, response.clone());
return response;
});
}).catch(() => {
return caches.match(event.request);
})
);
});Stale While Revalidate. The response is immediately returned from cache, while a background request is sent to the server to update the cache. The user sees content instantly, and the next request will get fresh data. Ideal for content that changes but where it is not critical if the user sees a version that is a few minutes old.
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(cached => {
const fetchPromise = fetch(event.request).then(response => {
return caches.open('pwa-dynamic-v1').then(cache => {
cache.put(event.request, response.clone());
return response;
});
});
return cached || fetchPromise;
})
);
});The manifest is a JSON file, usually manifest.json, linked via a <link> tag in the HTML page head. It tells the browser how the application should display when installed on a device: name, icons of different sizes, background color, theme color, orientation and display mode (browser, minimal UI, fullscreen).
{
"name": "PWA Demo App",
"short_name": "PWA Demo",
"description": "Ứng dụng PWA trình diễn",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#3f51b5",
"orientation": "portrait",
"icons": [
{
"src": "/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
]
}The display parameter determines how the app will look after installation. standalone hides the browser address bar and navigation panel — the app looks native. fullscreen uses the entire screen without any browser elements. minimal-ui leaves minimal browser elements (back and refresh buttons).
Icons should be prepared in at least two sizes: 192x192 and 512x512 pixels. PNG format with support for the purpose parameter (e.g., maskable) allows the browser to adapt the icon to the device shell shape — on Android, the icon may be cropped to a rounded square. After connecting the manifest, Chrome on Android starts showing the "Add to Home Screen" banner when conditions are met: HTTPS, registered Service Worker, valid manifest and at least two visits with a 5-minute interval.
PWA can send push notifications like a native app. Two APIs are used: Push API (server-side sending) and Notification API (display on device). Service Worker receives the push event even if the app is not open, and can display a notification with a title, text and icon.
To receive push notifications, the user must grant permission:
Notification.requestPermission().then(permission => {
if (permission === 'granted') {
console.log('Push allowed');
}
});Service Worker subscribes to the push event and displays the notification:
self.addEventListener('push', event => {
const data = event.data.json();
self.registration.showNotification(data.title, {
body: data.body,
icon: '/icons/icon-192x192.png',
badge: '/icons/badge-72x72.png',
actions: [
{ action: 'open', title: 'Mở' },
{ action: 'close', title: 'Đóng' }
]
});
});
self.addEventListener('notificationclick', event => {
event.notification.close();
if (event.action === 'open') {
clients.openWindow('/');
}
});To send push from the server, the Web Push protocol (VAPID) is used. The server encrypts the message and sends it through the browser push service (Firebase Cloud Messaging for Chrome, Mozilla Autopush for Firefox). The user's subscription (PushSubscription object) must be saved on the server during registration.
PWAs must be fast. Google Lighthouse is an audit tool that checks PWA against a checklist: Service Worker registration, 200 response with offline access, valid manifest, HTTPS, correct icon sizes, fast loading on slow networks. The minimum passing score for PWA is 80 out of 100 on the Lighthouse scale. Apps with a score below 80 do not receive the Chrome install banner.
Key performance metrics for PWAs — First Contentful Paint (FCP) under 1.8 seconds, Largest Contentful Paint (LCP) under 2.5 seconds, Time to Interactive (TTI) under 3.5 seconds. Service Worker can significantly improve FCP and LCP if it caches critical HTML and CSS during installation. The Cache First strategy for static assets and Stale While Revalidate for APIs gives the best user experience.
One of the main capabilities of PWA is working without internet. Service Worker intercepts requests and serves cached versions of pages. If the user tries to open a page that is not in the cache, a custom fallback page can be shown:
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(response => {
return response || fetch(event.request);
}).catch(() => {
return caches.match('/offline.html');
})
);
});Background Sync allows deferring data sending to the server if the device is offline. Service Worker saves the request and automatically sends it when the connection is restored:
self.addEventListener('sync', event => {
if (event.tag === 'sync-messages') {
event.waitUntil(sendMessages());
}
});
function sendMessages() {
return caches.open('pwa-pending-v1').then(cache => {
return cache.keys().then(requests => {
return Promise.all(requests.map(request => {
return fetch(request).then(() => cache.delete(request));
}));
});
});
}Chrome DevTools provides the Application panel, where you can debug Service Worker (registration, status, cache, push), check the manifest, clear the cache and create custom push notifications for testing. The Network tab with offline mode emulation helps test cache behavior. Lighthouse in the Audits panel provides a full PWA audit with recommendations.
Workbox is a library from Google that simplifies Service Worker creation. Instead of manually writing caching strategies, Workbox offers ready-made modules:
import { precacheAndRoute } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { StaleWhileRevalidate } from 'workbox-strategies';
precacheAndRoute(self.__WB_MANIFEST);
registerRoute(
/\.(?:png|jpg|jpeg|svg|gif)$/,
new StaleWhileRevalidate()
);Workbox integrates with bundlers (Webpack, Vite) and automatically generates a precaching manifest. PWA Builder (pwabuilder.com) by Microsoft generates all PWA files online: Service Worker, manifest, icons, HTML template.
FAQ
PWA is a website that, after installation on your phone, works like a regular app: it has an icon on the home screen, offline access, push notifications, fullscreen mode without the browser address bar. And you don't need to go to the App Store or Google Play — installation happens in one click from the browser.
A native app is written for a specific platform (Swift for iOS, Kotlin for Android), requires publication in a store and manual installation. PWA is a website written in HTML, CSS and JavaScript that can be installed on a device through the browser. PWA does not take up much space (only cache), updates automatically and is supported by all modern browsers.
Chrome, Firefox, Safari (since iOS 11.3), Edge, Samsung Internet, Opera. Service Worker is supported by all modern browsers except Internet Explorer. Safari has limited support for push notifications, but basic PWA capabilities (install to home screen, offline access) work.
Yes. To do this, you need to add a manifest (manifest.json), create and register a Service Worker (sw.js), switch the site to HTTPS, add meta tags and icons. The site itself does not require rewriting if it runs on JavaScript and serves HTML. If the site is fully server-side (MPA), you can add a Service Worker as a caching layer without changing the architecture.
When a user opens a PWA, the browser checks whether the Service Worker file on the server has changed. If the file differs by even one byte, the browser downloads the new Service Worker and runs its installation. The old Service Worker continues to work until all app tabs are closed. After the tabs are closed, the new Service Worker activates and replaces the old one. The user can force update the app by reloading the page.
PWA is justified if the goal is to reach the maximum audience without development costs for two platforms, if the app does not require deep access to hardware (Bluetooth, NFC, background camera), if content changes frequently. For complex applications (games, video editors), a native app remains preferable. PWA is often used as an additional channel or as an MVP before a full-fledged app.
Summary
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm