Progressive Web Apps (PWAs)
PWAs give web applications native capabilities such as offline access, desktop installation, Push notifications, and background synchronization. This article explains their three core components, implementation steps, and platform support in 2026, including Web Push on iOS.
Key Points
- Topic Focus: React、TypeScript
- Suitable for: indie hackers, full-stack engineers, AI application developers
- Core value: quickly understand background, practical paths, common pitfalls, and engineering advice
“Does anyone still use PWAs?” This is one of the questions I have heard most often over the past two years. After Apple repeatedly cut back PWA capabilities on iOS in 2018, the concept did lose momentum for a while. But by 2026, things have changed: iOS 16.4 finally filled the biggest gap by adding Web Push, while Chrome and Edge have long offered a complete set of PWA capabilities.
So it is worth revisiting PWAs. This article will not provide an encyclopedia-style explanation of “what a PWA is.” Instead, it covers how the three core components work together, how to implement them, and which scenarios genuinely call for a PWA in 2026.
1. What Problem Does a PWA Actually Solve?
In one sentence: it gives a web application a native app experience without requiring an App Store listing.
Specifically, it provides four capabilities:
- Offline access: the app still opens without a network connection and reads content from the cache.
- Desktop installation: users can “install” the web application as a desktop icon and launch it directly in full screen, without the browser chrome around the address bar.
- Push notifications: the server can notify users even when the application is not open.
- Background synchronization: when connectivity returns, actions performed offline, such as publishing a post or submitting a form, are automatically synchronized with the server.
These four capabilities used to be exclusive to native apps. The PWA bet is that for most content and utility applications, users simply do not want to download an 80 MB app for a wallpaper or a notepad. Add these four capabilities to the Web, and the experience is already good enough.
Pay attention to “good enough”: PWAs do not replace native apps. They replace “things that should never have been apps in the first place.” I will expand on this later.
2. The Three Core Components: Service Worker / Manifest / Web Push
A PWA is not a single technology. It combines three components.
1. A Service Worker (SW) is the brain. It is JavaScript that runs in the browser background, independently of the page, and can intercept network requests. Offline access, caching, receiving Push messages, and background synchronization all depend on it. An SW has several counterintuitive characteristics: it cannot manipulate the DOM directly; it has its own lifecycle (install → activate); and the browser terminates it when it is not needed, then wakes it when necessary. Therefore, do not store runtime state in an SW; all state must go through Cache Storage or IndexedDB.
2. The Web App Manifest provides the identity. It is a JSON file that tells the browser the site’s name, icon, theme color, and whether it should launch in full screen. Without it, the browser treats the site as an ordinary web page. Add it and a registered SW, and an “Install” button appears beside the address bar.
3. The Web Push API provides the voice. Based on the VAPID protocol, it lets the server send messages to user devices through push services: Chrome uses FCM, Firefox and Edge use their own services, and Safari uses APNs. The SW receives the message in the background and displays a notification. Users can receive Push notifications without opening the application, making this the part of the PWA experience that most closely resembles a native app.
3. Implementation: Build an Installable PWA from Scratch
It takes four steps.
Step 1: Write manifest.json. These are the most important fields:
{
"name": "CodeNodeX",
"short_name": "CodeNodeX",
"description": "AI 开发者社区",
"start_url": "/",
"display": "standalone",
"background_color": "#0a0a0a",
"theme_color": "#0a0a0a",
"icons": [
{ "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any maskable" }
]
}
display: "standalone" is critical: after installation, the app launches without browser chrome and looks like an independent app. purpose: "maskable" allows Android to adapt the icon to different shapes. Without it, Android forcibly crops the icon into a circle, which looks bad.
Add this to the HTML: <link rel="manifest" href="/manifest.json">.
Step 2: Register the Service Worker. Do this in the entry file, not the SW file:
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js').catch(console.error);
});
}
Step 3: Write the SW and choose a caching strategy. This is what truly determines the offline experience. The most commonly used strategies are:
- Cache First: use this for static assets such as JS, CSS, fonts, and icons. Check the cache first and return a hit immediately; on a miss, request the resource from the network and store it. This is suitable for assets that do not change.
- Network First: use this for dynamic content such as API responses and article bodies. Request the network first, then use the response and update the cache on success; fall back to the cache on failure. This gives users the latest data while preserving offline access.
- Stale While Revalidate: a compromise that returns the cache immediately for speed while fetching a fresh version in the background to update the cache for the next request. It suits noncritical resources that should remain reasonably fresh.
Here is a minimal usable SW:
const CACHE = 'codenodex-v1';
const ASSETS = ['/', '/styles.css', '/app.js', '/icon-192.png'];
self.addEventListener('install', (e) => {
e.waitUntil(caches.open(CACHE).then((c) => c.addAll(ASSETS)));
});
self.addEventListener('fetch', (e) => {
const { request } = e;
// 动态接口走 network-first
if (request.url.includes('/api/')) {
e.respondWith(
fetch(request).then((res) => {
const copy = res.clone();
caches.open(CACHE).then((c) => c.put(request, copy));
return res;
}).catch(() => caches.match(request))
);
return;
}
// 其余走 cache-first
e.respondWith(
caches.match(request).then((hit) => hit || fetch(request).then((res) => {
const copy = res.clone();
caches.open(CACHE).then((c) => c.put(request, copy));
return res;
}))
);
});
Step 4: Enable HTTPS. An SW must be registered over HTTPS, with localhost as the exception. Without HTTPS, nothing else matters. This is also why some internal tools cannot become PWAs until their certificate issues are resolved.
4. Offline Strategy: App Shell + Dynamic Data
A genuinely useful offline experience does not package the entire site. It uses layers.
The App Shell model precaches the application’s “skeleton”: the HTML shell, navigation, styles, and core JS. When users open the app offline, the Shell appears instantly, while the content area displays an “Offline” placeholder or previously cached data. This model works particularly well for SPAs and content sites because it decouples the “UI framework” from the “data.” The former must work offline; the latter is best effort.
Dynamic data follows one of two paths. The first is the stale-while-revalidate strategy described above, which gives users an experience that “looks like data is available.” The second is Background Sync: when users submit a comment or form offline, the browser remembers the operation, and the SW retries it automatically when connectivity returns. This is one of the closest approximations of a native app experience because submissions are not lost when the network becomes unstable.
5. Installation Experience: Add to Home Screen
Once the Manifest is configured and the SW is registered, Chrome and Edge trigger the beforeinstallprompt event. You can use it to provide a custom “Install” button instead of waiting for the browser’s own prompt, which has a very low conversion rate:
let deferredPrompt: BeforeInstallPromptEvent | null = null;
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
deferredPrompt = e;
showInstallButton(); // 你自己的 UI
});
installButton.onclick = async () => {
if (!deferredPrompt) return;
deferredPrompt.prompt();
await deferredPrompt.userChoice;
deferredPrompt = null;
};
After installation, another icon appears on the desktop or home screen. The app launches directly in full-screen standalone mode and is indistinguishable from a native app in the taskbar or Dock. That is the ultimate goal of the PWA “installation experience.”
6. Platform Support in 2026: The Missing Pieces Are Finally Here
This is the point I most want to emphasize because it directly determines whether you can confidently adopt a PWA.
- Chrome / Edge (desktop): full support. Installation, SW, Push, background synchronization, file systems, periodic synchronization, and even protocol handlers are all available.
- Android Chrome: equally complete. Push uses FCM, and the installation experience is the most mature. Android offers the best PWA experience of any platform.
- iOS / iPadOS 16.4+: a historic breakthrough because it finally supports Web Push. PWAs installed on the home screen can receive Push notifications like native apps through APNs. Users must launch the PWA once from its home-screen icon before granting permission. Before this release, the greatest limitation of iOS PWAs was that they could not receive notifications after installation, eliminating roughly half their value. That gap is now closed.
- Desktop Safari (macOS): supports manifests and basic SW functionality, but macOS Safari still does not support Web Push. Only iOS 16.4+ supports it. Therefore, a PWA still cannot cover Push requirements for Mac users.
To summarize the state of the platform in 2026: mobile PWAs are viable products, not toys. iOS 16.4 was the dividing line. Before it, PWAs were possible but incomplete; now the loop is finally closed.
7. PWA vs. Native vs. Cross-Platform: Do Not Choose the Wrong Track
I use one criterion to choose among these three options: does the application actually need native capabilities?
- PWAs are suitable for content consumption such as news, blogs, and communities; utilities such as notepads, converters, and calculators; and admin interfaces with light interaction. These applications do not need Bluetooth, sensors, or persistent background execution, and users are not willing to download a dedicated app for them.
- Native apps are suitable for graphics-intensive workloads such as games and video editing; system-level integration such as health data, cameras, and AR; intensive background operation, where instant messaging requires native Push plus a long-lived connection; and applications extremely sensitive to launch performance, such as a camera that must open instantly.
- Cross-platform apps (RN/Flutter) are the compromise: you need an App Store listing but want one codebase to run on both platforms. The tradeoffs are package size, initial-screen performance, and the complexity of native bridges.
My view is straightforward: a PWA is not a path to “replacing native”; it is a path to “doing more on the Web.” It brings scenarios that once required an app back to the Web. But native remains irreplaceable for workloads that truly depend on hardware, operating-system integration, or background execution. Do not believe that “PWAs will kill native,” and do not believe that “PWAs are useless.” They are useful when applied in the right place.
8. Next.js Projects: next-pwa Is the Fastest Shortcut
If you use Next.js, as this site does, do not hand-write everything above. Under the App Router, next-pwa can automatically generate the manifest, register the SW, and handle caching strategies:
// next.config.ts
import withPWA from 'next-pwa';
export default withPWA({
dest: 'public',
register: true,
skipWaiting: true,
})(nextConfig);
It uses Workbox, Google’s SW toolkit, under the hood. Caching strategies such as precaching, runtime caching, and various fallbacks are already built in. You only need to declare which strategy applies to each path in the runtimeCaching configuration. For most projects, hand-writing an SW means reinventing a worse version of Workbox. Unless your requirements are highly customized, use it directly.
Summary
- A PWA’s value is that it gives the Web four native app capabilities: offline access, installation, Push, and background synchronization. It does not replace native; it keeps things that should never have been apps on the Web.
- The three core components have clear responsibilities: the Service Worker is the brain that intercepts requests, caches resources, and receives Push messages; the Manifest is the identity that defines the name, icon, and full-screen mode; and Web Push is the voice that receives notifications.
- Choose among three caching strategies: cache-first for static assets, network-first for dynamic endpoints, and stale-while-revalidate for neutral resources. App Shell plus background synchronization is the standard combination for offline experiences.
- Platform support in 2026: Chrome, Edge, and Android provide comprehensive support, while iOS 16.4+ finally supports Web Push, the key to completing the PWA experience. However, macOS Safari still does not support Push, which remains a known limitation.
- Use
next-pwain Next.js projects. Workbox already handles the heavy lifting underneath, so do not hand-write it.
One final point: a PWA should not be a debate topic in a technology-selection meeting. It should be “an experience this site should have had all along.” Users will not praise a content site for adding offline access and a desktop icon, but installing one fewer app and granting one fewer Push permission quietly makes the experience a little better.
Related
Related Content
Article
Is Next.js App Router a Good Fit for a Technical Community?
Technical trends, engineering practices, and independent development perspectives centered on Next.js and React.
Article
SEO Design for a Content Community with Next.js
Technical trends, engineering practices, and an indie developer's perspective on Next.js and TypeScript.
Article
The Engineering Value of TypeScript in AI Application Frontends
Technical trends, engineering practices, and an independent developer perspective on TypeScript and React.
Article
What Value Do Server Components Offer Content Sites?
Technical trends and engineering practices around Next.js and React, from an independent developer's perspective.