Use a HACKDAY to PWA a React app

Craig Taub
6 min readApr 15, 2018

My company recently had a hack-day. Seeing as how service-worker support is growing (I believe iOS 11.3 has had a big update) and the evidence for the success using progressive web applications is everywhere (Googles showcase documents include dozens of statistics), myself and a few colleagues decided to band together to work on making our react/redux/express application more compliant. Or at least seeing what we could get done in 1 day.

TLDR;

  1. Be organised
  2. Manifest
  3. Offline homepage
  4. Asset caching
  5. Runtime API cache
  6. Interactive offline/Optimistic UI
  7. Pre-cache popular journeys

1. Be organised

To start with we came up with our list of tasks and decided to parallise them where possible to utilise the best use of time. Having several people working in the same area can be a disaster. Here is how we divided the work between 2 teams.
1. Offline homepage + Asset caching -> pairing
2.1. Runtime API cache -> team A
2.2. Interactive offline -> team B
3.1. Pre-cache popular journeys -> team A
3.2. Manifest -> team B

2. Manifest

This covers icons and splash screen. It gives users an “add icon to homepage” message on screen as well as introduces a “theme” colour for the site on mobile. The splash is something the user sees when they open the app via the saved icon. This has the overall effect of making it feel like a native app.

Our manifest.json looked like:

{  
"name": Our app",
"short_name": "Our app",
"start_url": "/?utm_source=homescreen",
"display": "standalone",
"background_color": "<COLOUR>",
"theme_color": "<COLOUR>",
"orientation": "portrait",
"description": "Our app",
"icons": [{
"src": "/128x128.png",
"sizes": "128x128",
"type": "image/png" },{
"src": "/192x192.png",
"sizes": "192x192",
"type": "image/png" },{
"src": "/384x384.png",
"sizes": "384x384",
"type": "image/png" },{
"src": "/512x512.png",
"sizes": "512x512",
"type": "image/png" }]
}

3. Offline homepage

For this we used Googles Workbox tool. It is amazing for handling assets and caching strategies.
We adding a basic pre-caching strategy which will cache the homepage immediately. Future requests to these URL’s will use the cache.

Our service-worker.js looks like this:

importScripts('https://storage.googleapis.com/workbox-cdn/releases/3.0.0/workbox-sw.js');workbox.precaching.precacheAndRoute([  
{ url: '/index.html' },
{ url: '/' }
]);

We did of course have to register our service worker too. In our React application it looks something like:

<script type="text/javascript" dangerouslySetInnerHTML={ { __html:
`window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js');
});`
} } />

4. Asset caching

This involves adding listeners for caching any JS or CSS assets which are requested.

The Workbox library gives us the option of using the staleWhileRevalidate strategy, which will serve the cache but then go off and check for a never version, updating the cache for future usage.

This also defines a namespace for each collection so they are easily managed (i.e. js-cache, css-cache).

// Caching JS assets
workbox.routing.registerRoute(
new RegExp('.*\.js'),
workbox.strategies.staleWhileRevalidate({
cacheName: 'js-cache'
})
);
// Caching CSS assets
workbox.routing.registerRoute(
new RegExp('.*\.css'),
workbox.strategies.staleWhileRevalidate({
cacheName: 'css-cache'
})
);

5. Runtime API cache

Again we used Workbox for this. It listens to all API requests made at runtime and adds those to the cache.

Below uses a cacheFirst strategy so the service worker will check for the cache BEFORE any network requests. It also introduces a cache expiry and entries limit so we don’t max out the users memory or hold responses for too long. We can configure it to only cache certain response types, in our example it is 200 status codes.

The strategy is something which needs further thought when it comes to applying it in production environment e.g. some endpoints may need to be excluded.

workbox.routing.registerRoute(
"<our API url>",
workbox.strategies.cacheFirst({
cacheName: 'api-cache',
plugins: [
new workbox.expiration.Plugin({
maxEntries: 50,
maxAgeSeconds: 5 * 60, 5 minutes
}),
new workbox.cacheableResponse.Plugin({
statuses: [0, 200],
}),
],
}),
);

The main caveat is that you MUST be using the FETCH API on your front-end for ajax requests i.e. if you use XHRHttpRequest (via a library or natively) it will not work.

6. Interactive offline/Optimistic UI

What is an Optimistic UI?

The basic premise of Optimistic UI is that the UI will make changes on the assumption that remote operations were a success. Typically UI changes are only reflected AFTER a change has been accepted externally. e.g.

Normal UI:
User clicks CTA -> AJAX request to server -> UI updated
Optimistic UI:
User clicks CTA -> UI updated -> AJAX request to server

What this means is that your application can become interactive/functional even if it is online.

For example an e-commerce site could let users add items to their basket, then navigate to their basket, all while the user was offline. Then on network connection it can sync these changes with the server. If there is any issues it could delivery a message to the user explaining (just one way to handle it).

Introducing redux-offline:

Redux-offline is a library which can help with this (only applies to applications using redux unfortunately). With it we can be explicit about what to do if the user loses network.

It is setup by adding an offline property for every action. It includes what to fire if network is lost and also what to fire if they reconnect again.

This means we can remove the networks dependency and focus on the experience. Covering all types of UX and giving us the option of a more optimistic ui update with network-resilience.

From what I can tell it works by storing the actions offline metadata in an internal array (a queue) which is processed on re-connection. All required action-based details are available in the offline property.

7. Pre-cache popular journeys

This is something I was passionate about as it could creatively solve a problem for users. For example on a product page with recommendations listed, we could pre-cache all the recommendation items so regardless of the users network they will always be able to load any recommendation clicked on (from that page). This was not really service-worker related and the above example is actually the “popular journey use-case” we decided to implement.

As we had the “Runtime API cache” it was just a matter of making a full API request for each recommendation item on a loading product page (not just its thumbnail details). This would warm the cache with those products content, which any future page load of that item would use. So the actual mechanics of getting this to work was very simple.

BEFORE: 
A. Request products recommendations
AFTER:
A. Request products recommendations
B. Request product details for each recommendation

This area has a lot of potential for growth and creativity. A popular journey can vary depending on the type of user. Offering the ability to introduce machine learning/profiling/patterning to decide exactly what to pre-cache and when.

Debugging

Something we found incredible useful was Chromes debugging tools for service worker and the Cache Storage API. With the cache it gives you all sorts of details including the contents of the cache and expiry. Making it easier to get to the bottom of why some content is in the state it is. You can easily remove single items or the entire namespaced collection.

Summary

We were incredibly pleased with the amount of work which we accomplished in a single day (FYI we completed it all). We feel without being so organised with our planning this would not have been possible. But it does highlight how possible it is to quickly get some PWA functionality added to a React application, especially utilising the open-source libraries available.

Our company was very impressed and excited about the possibilities with this technology. We plan to roll out these changes (with more discussions around the caching strategies).

I personally feel its a growing area with lots of room for creativity and innovation and look forward to the web becoming a more offline-friendly and network independent place.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Craig Taub
Craig Taub

No responses yet

Write a response