Cache Me Outside: Essential Caching Techniques for Building High-Performance SPAs
Single-Page Applications (SPAs) are fast becoming the most popular way to build a website. From a user’s perspective, navigation can feel seamless and page loads look and feel like a mobile experience. For a developer, the challenge then is to maintain a consistent user experience often by focusing on performance as a key metric. That’s where a good caching strategy can make your user’s day or break your site into a spinning loader of doom. In the next few paragraphs we will look at good caching strategies to keep your SPA fast, shiny, and new while enticing your users to come back for more.
The first rule of caching is don’t cache everything
Differentiate your caching needs for the various parts of your site, specifically the Index HTML versus the static assets. One of the elegant features of an SPA is that it is a single HTML file that acts as the site’s entry point dictating which assets to load. Setting headers in this file to Cache-Control: no-store, no-cache, must-revalidate, max-age=0 will prevent outdated versions and stale assets from sneaking into your freshly served site. For static assets (JS, CSS, Images), use HTTP cache headers like max-age to specify how long they sit in the browser's cache. If assets don’t change very often, set a long max-age=31536000 (a year) to minimize server requests and browser downloads. Otherwise, set the max-age to a day for stable assets, or seconds for more frequent updates.
Beyond the headers
After setting up the cache-control headers, another good practice is to implement version control so that the browser knows it is grabbing the newest version of your files. Leverage the SPA framework that includes a tool such as Webpack and appends a unique hash to your file names during the build process. This small but powerful change will invalidate any old cached versions held in the browser by simply letting it know these files are different and new. The image below shows the caching strategy for a rather large styling library resource, Bootstrap. Here we see the use of version control in the request, resulting in a 304 Not Modified response as the version has not changed, as well as setting a max-age of one year in the Cache-Control header so that the resource is only downloaded again when the Age of the request is lapsed. This combination of cache implementation avoids downloading a large resource from the server, and allows the site to move quickly to other resource requests.
Don’t cache everything in the browser
Consider any frequently used API responses, large images, or other assets that would increase the bandwidth of your site. This is where you can give the browser cache a rest and look to other methods to take over some of the heavy lifting. Server-side caching, Service Workers, and CDNs are a few options to consider. Server-side cache can be used to hold frequently accessed data such as product descriptions, predefined values, or other constants. This data is cached on the server and the cached version is used to respond to redundant API calls. Service Workers can be used to intercept network requests and manage offline functionality by caching API responses for offline access. A CDN, Content Delivery Network, will cache assets in physical proximity to users and improve performance by simply being geographically closer. Utilizing any of these techniques along with cache-control headers will give you an even greater impact on performance. (I do want to point out here that API responses can be held in the browser cache but this is a good opportunity to spread the caching love around!)
Set it, but don’t forget it!
Now that everything there is to cache is successfully cached, the next step is to plan how to undo all your hard work when that data becomes outdated. Set TTL (Time to Live) parameters on your data, keeping in mind that shorter TTLs do best for dynamic content that changes often, and longer ones for your static data. Clearing the cache is also necessary to keep things fresh. Tools like websockets or polling for updates from the server are your best bet here to automatically refresh or invalidate expired data.
A healthy caching strategy is a balancing act
Over-caching will produce stale data, while under-caching can risk slowing your site and leaving your users feeling neglected. Don’t be afraid to change something that’s not working. A good practice is to regularly assess cache performance and make adjustments as needed. Chrome developer tools offer several ways to help monitor and analyze your site’s performance. The Network tab will show you all the resource requests and the status codes for each. This is a good way to keep track of 304 (Not Modified) responses and to monitor when cache responses are generated.
Lighthouse is another tool that can be used to evaluate your page load times. Running Lighthouse initially and again after caching strategies are implemented can show how and where a cache can best be utilized. In addition, SEO and user engagement can improve by leveraging powerful caching strategies, because both Google and users appreciate when things work quickly, so watch your traffic and conversion rates over time and help that marketing team look good!
Use the right combination of all the tools available
There are many ways to solve this puzzle, so take time to plan out what your site needs. Track, monitor, and refine your strategy as you go. A cache can be your best ally for building performant websites and earn you the credit you deserve for all the hard work it took to get it there. So, now armed with these newly found strategies, it’s time to build those lightning-fast, user-friendly SPAs and make the internet fast again. Thanks for reading, and cache you next time!
When A CloudFront Origin Must Fail for Testing High Availability
7 min read