Why Your Cached JavaScript Is Still Slow and Incurs Performance Overhead
Web Developers often fixate on optimizing the delivery of assets to the end-user's device, and overlook the computation that takes place on the end-user's device once those assets arrive.
In modern web development, JavaScript-centric SPAs are flooding the user's device with heavy compute. When I meet with teams interested in improving their web product's performance, this concept of client-side compute is often overlooked and I'm met with the following common misconceptions:
- Our JavaScript is Cached in the Browser, so our page should be fast!
- Bundle size doesn't matter since the bundles are stored in cache!
- We just completed our React migration, so we were expecting sizeable performance improvements, especially when the JS is cached!
These couldn't be further from the truth!
The Important of Asset Delivery
Don't get me wrong, for optimal web performance, assets should be optimally delivered and cached!
Web application performance is fundamentally tied to the network and the speed at which we can deliver an asset to the end-user device.
However, even if a web application has all assets optimally delivered and/or cached, that doesn't mean that it will run fast or render quickly when it is needed during runtime on the end-user's device.
Web Application Bottlenecks
At a high level, there are two primary performance bottlenecks on the web:
- Networking - the round-trip time to acquire an asset or data payload from a remote server
- End-user Device Compute - the amount of computational overhead required on the end-user's device
The latter often overlooked, and nowadays, it's the most important aspect of web performance! When JavaScript is cached, there is no networking cost! However, there is plenty of end-user device compute cost.
Let's take a closer look at the (significant) compute associated with loading and executing cached JavaScript.
A Concrete Scenario
Consider the following React-based SPA:
<body>
<div id="root"></div>
<script src="/vendor-bundle.HASH.js" type="text/javascript"></script>
<script src="/app-bundle.HASH.js" type="text/javascript"></script>
</body>// app-bundle.HASH.js
ReactDOM.render(<MyApp />, document.getElementById('root'))A common and insightful optimization would be to apply Cache-Control
headers to the app-bundle and vendor-bundle JS files. One might even go further and use a Service Worker to
pre-install assets.
This would allow users who re-visit the web application to load the JavaScript files from an offline disk cache.
But what happens when these JavaScript files are loaded from cache?
System Overview
The following diagram overviews the process of loading a script from disk cache:
