Frontend caching strategies
Frontend caching is a largely underused technique, yet it can be a very powerful optimisation if used correctly. A good reason for caching data in the browser is because we can safely assume that a network request is more expensive and unpredictable than retrieving data from a local cache.
The following article will give a very high level view on a few frontend caching concepts. I will be using Axios in my examples, though the techniques shown should be transferable to whichever library you use.
Strategy 0: No caching
If you haven’t experimented with browser based storage before, this workflow will look familiar to you.
let dataaxios.get('/data').then((response) => {
data = response.data.data
})
This approach is usually the default and means you will never have to worry about the freshness of your data in your cache… because there is no cache, but you may want to consider network latency and the knock on affect to your user’s experience.
Strategy 1: Prefer API
We could start by making the network request more reliable by caching the last successful response data and falling back to that if the network resource is unavailable.
This approach is very common in offline first applications, but offers little in the way of performance gains.
let dataaxios.get('data').then((response) => {
data = response.data.data
}).catch(() => {
data = localStorage.getItem('data')
})
For simplicity, we are using localstorage to persist our data in these examples, but depending on your applications needs, you may want to look into a more grown up library, such as LocalForage.
Strategy 2: Prefer local
In this example, we will pull the data over the network, save it to our cache and then retrieve it locally on subsequent page loads.
This strategy would offer us large performance gains but will only be useful for data that is highly unlikely to change, think populating a country or language dropdown.
let data = localStorage.getItem('data')if (!data) {
axios.get('/data').then((response) => {
data = response.data.data
localStorage.setItem('data', response.data.data)
})
}
Strategy 3: Local then API
This strategy could be considered the best of both worlds, it will give you the performance gains of cached data retrieval and the reliability of eventually consistent data.
A brute force technique would be to preload your application with local data and then update both the application and the cache once the network request has completed.
let data = localStorage.getItem('data')axios.get('/data').then((response) => {
data = response.data.data
localStorage.setItem('data', response.data.data)
})
We can approach this a bit more intelligently though by only making the expensive network request if something invalidates our cache, this could be time based or could even be based on a version number.
const currentVersion = 2
let data = localStorage.getItem('data')
let version = localStorage.getItem('version')if (!version || version < currentVersion) {
axios.get('/data').then((response) => {
data = response.data.data
localStorage.setItem('data', response.data.data)
localStorage.setItem('version', currentVersion)
})
}
Summary
A well designed cache can not only improve your UX but also help relieve the pressure on your backend infrastructure and you will probably find that you use a combination of all of the above strategies depending on your application’s exact data needs.
If this article has piqued your interest, you might also want to consider looking into more fine grained network control with Service Workers and maybe even updating your cache using push based technologies, such as web-sockets.