Core Technologies of PWAs: Service Workers, Web App Manifests, and Caching Strategies
To fully implement a PWA, you need to understand its core technologies:
- Service Workers – JavaScript scripts that run in the background to handle caching, push notifications, and offline functionality.
- Web App Manifest – A JSON file that tells the browser how the app should behave when installed on a device.
- Caching Strategies – Various approaches to storing assets and API responses to improve performance and enable offline access.
Service Workers
A service worker enables background processes like caching and push notifications. The following example demonstrates a cache-first strategy:
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((cachedResponse) => {
return cachedResponse || fetch(event.request);
})
);
});
This ensures that previously loaded resources are served from cache first, reducing network latency.
Web App Manifest
The manifest defines how the app should appear and behave. A sample manifest.json
:
{
"name": "Rails PWA",
"short_name": "RailsPWA",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#000000",
"icons": [
{
"src": "/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
Caching Strategies
Common caching strategies include:
- Cache-First: Serve content from cache, fetch updates in the background.
- Network-First: Always try fetching from the network, fallback to cache if offline.
- Stale-While-Revalidate: Serve cache first, but update it in the background.
Implementing these strategies ensures fast load times and offline capabilities.
Example: Implement Caching Strategies in Service Worker
Create a service_worker.js
file and define different caching strategies.
const CACHE_NAME = 'rails-pwa-cache-v1';
const ASSETS_TO_CACHE = [
'/',
'/offline.html',
'/assets/application.css',
'/assets/application.js'
];
// Install Event - Cache assets
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME).then(cache => {
return cache.addAll(ASSETS_TO_CACHE);
})
);
});
// Activate Event - Cleanup old caches
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cache => {
if (cache !== CACHE_NAME) {
return caches.delete(cache);
}
})
);
})
);
});
// Fetch Event - Implement caching strategies
self.addEventListener('fetch', event => {
const url = new URL(event.request.url);
// Cache-First Strategy (for static assets)
if (url.origin === location.origin && ASSETS_TO_CACHE.includes(url.pathname)) {
event.respondWith(
caches.match(event.request).then(response => {
return response || fetch(event.request);
})
);
return;
}
// Network-First Strategy (for API calls)
if (url.pathname.startsWith('/api/')) {
event.respondWith(
fetch(event.request).then(response => {
return caches.open(CACHE_NAME).then(cache => {
cache.put(event.request, response.clone());
return response;
});
}).catch(() => caches.match(event.request))
);
return;
}
// Stale-While-Revalidate Strategy (for dynamic pages)
event.respondWith(
caches.match(event.request).then(cachedResponse => {
const fetchPromise = fetch(event.request).then(networkResponse => {
caches.open(CACHE_NAME).then(cache => {
cache.put(event.request, networkResponse.clone());
});
return networkResponse;
});
return cachedResponse || fetchPromise;
})
);
});