PWA: Web as App
I Hated App Stores
While building my service, I had a problem. Users kept requesting, "I want to add this to my home screen." But making a native app... just thinking about it gave me a headache.
- iOS app: Need to learn Swift
- Android app: Need to learn Kotlin
- App store review: Takes days
- Update deployment: Wait for review again
- Commission: 30% of revenue
I thought, "I already built it as a web app, do I really need to make a native app too?" Then I discovered PWA (Progressive Web App). They said it's technology that makes web work like an app.
I was skeptical at first. "How can web become like an app?" But when I actually built one... I was amazed.
First Challenge: Working Offline
The first thing I tackled with PWA was offline support. Regular websites just show the "No internet connection" dinosaur game when offline. But PWAs are supposed to work offline.
I wondered how this was possible, and the key was Service Worker. Simply put, it's a JavaScript file that acts as a bridge between the browser and server. It intercepts network requests, checks cached data first, and only requests from the server if not found.
When I first registered a Service Worker, I did it like this:
// main.js - Register Service Worker
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('Service Worker registered:', registration);
})
.catch(error => {
console.log('Service Worker registration failed:', error);
});
});
}
And the actual Service Worker file looked like this:
// sw.js - Service Worker implementation
const CACHE_NAME = 'my-pwa-v1';
const urlsToCache = [
'/',
'/index.html',
'/styles.css',
'/app.js',
'/logo.png'
];
// Install phase: Save necessary files to cache
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => {
console.log('Cache opened');
return cache.addAll(urlsToCache);
})
);
});
// Intercept network requests
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then((response) => {
// Return cached data if available
if (response) {
return response;
}
// Otherwise fetch from network
return fetch(event.request);
})
);
});
After building this, my website worked even when I disconnected the internet! I was genuinely amazed when I first saw it. "Wow, this actually works?"
Add to Home Screen: Like a Real App
After implementing offline support with Service Worker, the next step was add to home screen. When users tap "Add to Home Screen" in the browser menu, an icon appears just like a real app.
For this, I needed a Web App Manifest file. One JSON file defines the app's name, icon, colors, etc.:
{
"name": "My Service",
"short_name": "Service",
"description": "My service built as PWA",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#2196F3",
"icons": [
{
"src": "/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
Then link this manifest file in HTML:
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#2196F3">
After this, when I opened my site on mobile, an "Add to Home Screen" popup appeared. When added, an icon appeared just like a real app, and clicking it opened in fullscreen without the browser address bar.
"display": "standalone" was the key. This hides the browser UI and makes it look like an app.
Push Notifications: Communicating with Users
The most surprising thing about PWA was push notifications. Sending push notifications from a website! This was also thanks to Service Worker.
First, request notification permission from the user:
// Request notification permission
async function requestNotificationPermission() {
const permission = await Notification.requestPermission();
if (permission === 'granted') {
console.log('Notification permission granted');
// Register push subscription
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: 'YOUR_PUBLIC_KEY'
});
// Send subscription to server
await sendSubscriptionToServer(subscription);
}
}
Then handle push messages in Service Worker and display notifications:
// sw.js - Handle push notifications
self.addEventListener('push', (event) => {
const data = event.data.json();
const options = {
body: data.body,
icon: '/icons/icon-192x192.png',
badge: '/icons/badge-72x72.png',
vibrate: [200, 100, 200],
data: {
url: data.url
}
};
event.waitUntil(
self.registration.showNotification(data.title, options)
);
});
// Handle notification click
self.addEventListener('notificationclick', (event) => {
event.notification.close();
event.waitUntil(
clients.openWindow(event.notification.data.url)
);
});
With this, I could send notifications even when users weren't viewing my site. Just like a native app!
Caching Strategies: Fast Loading
An important concept I learned while using Service Worker was caching strategies. Caching everything is good for offline but prevents updates, while caching nothing makes it unusable offline.
The strategy I used:
1. Cache First: Static files like images, CSS, JS
self.addEventListener('fetch', (event) => {
if (event.request.url.match(/\.(jpg|png|css|js)$/)) {
event.respondWith(
caches.match(event.request)
.then(response => response || fetch(event.request))
);
}
});
2. Network First: Dynamic content like API data
self.addEventListener('fetch', (event) => {
if (event.request.url.includes('/api/')) {
event.respondWith(
fetch(event.request)
.then(response => {
// Save network response to cache
const responseClone = response.clone();
caches.open(CACHE_NAME).then(cache => {
cache.put(event.request, responseClone);
});
return response;
})
.catch(() => {
// Use cache on network failure
return caches.match(event.request);
})
);
}
});
This way, static files load quickly, API data stays fresh, and users can still see the last data offline.
Pros and Cons from Real Experience
After building a PWA and applying it to my service, the pros and cons were clear.
Pros
1. Deployment is super easy
- Just upload to web server
- No app store review needed
- Updates reflect immediately
2. Low development cost
- Only need web technologies
- Don't need separate iOS/Android apps
- One codebase for all platforms
3. Great user experience
- Works offline
- Fast loading speed
- Can add to home screen like an app
Cons
1. Limited native features
- Limited hardware access like camera, Bluetooth
- Not as flexible as native apps
2. iOS support is lacking
- Safari's PWA support is weaker than Android
- Push notifications barely worked on iOS (only recently started supporting)
3. No app store visibility
- Users can't find it by searching app stores
- Must visit website first to install
One-Line Summary
PWA is technology that makes web work like an app using Service Worker and Web App Manifest. It enables app features like offline support, add to home screen, and push notifications on the web, and deployment without app stores makes development and updates easy. While not as powerful as native apps, it's sufficient for most web services and, most importantly, much easier to build.