Best Caching strategies — Progressive Web App (PWA)

Libin V Babu
Animall Engineering
5 min readJan 19, 2021

The main challenge I’ve faced while building a web app was the page loading speed. The best way to speed up a page is to load the assets from the cache and thus avoid network calls. This is really important when your users are on a slow network. This time I’ve decided to jump on to service workers and make full use of its caching strategies.

Popular caching strategies are:

  1. Cache first, Network fallback.
  2. The network first, Cache fallback.
  3. Stale while revalidate
  4. Network only
  5. Cache only

I’ll briefly explain what these strategies are and where we can apply these.

Cache first, Network fallback.

The service worker will loads the local (cached) assets (HTML, CSS, images, fonts, etc.), if possible, bypassing the network. If cached content is not available, the service worker returns a response from the web instead and caches the network response. This strategy can be used when dealing with remote resources that are very unlikely to change, such as static images.

The network first, Cache fallback.

In this strategy, the service worker will check the network first for a response and, if successful, returns current data to the page. If the network request fails, then the service worker returns the cached entry instead. Use this when data must be as fresh as possible, such as a real-time API response, but you still want to display something as a fallback when the network is unavailable.

Stale while revalidate

The stale-while-revalidate pattern allows you to respond to the request as quickly as possible with a cached response if available, falling back to the network request if it’s not cached. The network request is then used to update the cache. This is a fairly common strategy where having the most up-to-date resource is not vital to the application.

Network only

In this, the service worker will only check the network. There is no going to the cache for data. If the network fails, then the request fails. This can be used when only fresh data can be displayed on your site.

Cache only

The data is cached during the install event so that you can depend on the information being there. This can be useful if you have your own precaching step.

While you can implement these strategies yourself manually, using workBox is recommended for service worker caching. Workbox is a set of libraries that can power a production-ready service worker for your Progressive Web App.

The approach I’ve taken at Animall are:
1. Pre-cache the home page assets when the user lands on the onboarding page through the install event. With this, once the user completes the onboarding, all the static assets needed to load the home page are already available in the cache.

self.addEventListener(‘install’, async function (event) {
event.waitUntil(caches.open(‘assets’).then(function (cache) {
return cache.addAll(filesToCache);
}));
});

filesToCache in the above code is an array of files to be cached at the install event.

2. Network-only strategy to handle all navigations.

var networkOnly = new NetworkOnly();
var navigationHandler = async function navigationHandler(params) {
try {
return await networkOnly.handle(params);
} catch (error) {
return caches.match(FALLBACK_HTML_URL, {
cacheName: 'offline'
});
}
};
// Register this strategy to handle all navigations.
var navigationRoute = new workbox.routing.NavigationRoute(navigationHandler);
registerRoute(navigationRoute);

This ensures that the pages are loaded from the network only and fall back to an offline page if the internet is not available.

3. Stale While Revalidate strategy for CSS and js assets

registerRoute(function(_ref2) {
var request = _ref2.request;
return request.destination === 'style' || request.destination === 'script';
}, new StaleWhileRevalidate({
cacheName: 'assets',
plugins: [new CacheableResponsePlugin({
statuses: [200]
})]
}));

All the CSS/js assets are loaded from the cache and returns to the user immediately. Same time, it will check for any updates also from the network as well. If the file is updated in the network, the service worker will update the cache. So next time, when the user loads the same page, he will get the updated version.

4. Cache First for the images

registerRoute(function(_ref) {
var request = _ref.request;
return request.destination === 'image';
}, new CacheFirst({
cacheName: 'images',
plugins: [new CacheableResponsePlugin({
statuses: [200]
}), new ExpirationPlugin({
maxEntries: 50,
maxAgeSeconds: 60 * 60 * 24 * 30, // 30 Days
purgeOnQuotaError: true
})]
}));

The images we use are usually not updated on the server frequently. So I’ve used the cache first strategy for loading the static images with few options as well. Since the images can bloat your cache disk quickly, we have put a maximum image limit in the cache with maxEntries. Use purgeOnQuotaError along with this to make sure that the cache is cleared when the quota exceeds. Also, I’ve applied maxAgeSeconds to expire the image cache.

If you are excited about solving similar problems, Animall is looking for people like you to build the next billion users' platform. Animall is a top tier VC funded startup building an online platform to empower millions of dairy farmers, ushering in the next digital white revolution! You can find the opportunities here or shoot an email to mission@animall.in.

--

--