Web Application Performance Optimization
There are countless case studies which have shown that improving time to interactive and the speed of your site is one of the best ways to increase and retain the number of users. A couple great resources on the topic:
-
https://wpostats.com contains a repository of case studies that show the importance of web performance optimizations and is a great resource.
-
Google Developers section on Web Fundamentals has a great section on why web performance matters and also cites numerous case studies that show how important performance is to both retaining users and improving conversions.
If reading case studies and Google Developer guides is not convincing enough for you (you need to hear revenue projections), Google also provides a great tool to measure your application's performance and then compare this to your competitors. This is especially relevant for e-commerce sites, because it uses the average order amount per visit to attain estimated revenue projections for each 100ms improvement in page load times. After you've measured your apps performance and compared it to a competitor, it presents the impact a faster site can have on your business in terms of annual revenue.
Top web application performance bottlenecks
- Amount of Javascript on the initial page load
- Amount of CSS on the initial page load
- Amount of network requests on the initial page load
- Inefficient database queries
- Overly-complex server-side payload data sizes, manipulations, look-ups and service calls
It's fairly common to assume the 5th or 4th item in this list should be placed first
Performance budget
A performance budget will become increasingly necessary as many companies expand across the world and into developing markets.
5G has really just begun in the US at the time of writing, but in places like India 4G speeds are still as slow as 6Mbps (2018). In fact, the United states has one of the slowest 4G speeds across the world falling into the bottom 31% of countries in the 2018 Open Signal report with an average 4G speed of 16.3Mbps.
So, what's a realistic performance budget?
A performance budget can be many different things. It really is kind of vague. It could be a goal for page load times (like 3 seconds on 3G connections with mid-market mobile devices). It could also be a goal in terms of code size (like 300kb uncompressed Javascript). The specific performance budget that fits for you and your team can vary. Hopefully the information in this post can help you come up with a worthwhile budget for your app.
Reducing the amount of Javascript on initial load—how?
Code Splitting
Splitting out page critical code into async chunks (at build time) and importing them asynchronously and on demand as the user navigates around your site.
Webpack gives us a much easier interface for Code splitting with version 4 by returning a promise with any call to import('./<filePath>')
, though it can be done using version 3 with require.ensure.
// pages/home.js
import NextPageNavigationButton from '../buttons/nextPage';
const getComponent = () => import('./src/asyncComponent.js');
NextPageNavigationButton.on('click', () => {
// dynamically load the component via network request when warranted
getComponent().then(module => {
const target = document.getElementById('placeWhereComponentShouldGo');
module.initComponent(target);
});
});
Tree Shaking
Removal of dead code (within module scope) by detecting via import and export statements whether code modules are exported and imported for use between modules. If a module is unused, it is removed from the bundle. This is a great reason to upgrade to Webpack v4 as it's only available in this version.
In this below example, the function fly is removed from the bundle produced by Webpack or Rollup:
// modules.js
export function drive(props) {
return props.drive
}
export function fly(props) {
return props.mile
}
// modules.js
--------------------------------------------
// main.js
import { drive } from modules;
// some code
render() {
return (
<div>
<ul>
drive({ car: event.target })
</ul>
<dive>
)
}
// main.js
Browser Code Coverage
A measurement of the amount of code that is utlized by the browser is called Code Coverage. Since 2017, Code Coverage has been supported in Chrome Developer Tools and offers an excellent resource to reduce the amount of dead code in your application. Using the Coverage Drawer, one can identify line-by-line what code is being used at each page. This is a great tool for also identifying and removing unused CSS.
Webpack Bundle Analyzer
Disclaimer: Using Giffs is not ideal. They are huge! Stick to jpg, png files, or mp4s for animations.
https://github.com/webpack-contrib/webpack-bundle-analyzer
This plugin can help you to visualize the effect you are having with your Code Splitting and Tree Shaking (Tree Shaking is only enabled for Webpack in production mode).
Network request sizes
Say, for example, you load address metadata containing all address labels, validations, formats, and translations for all worldwide addresses in your application on the initial load via server-side request to a different API. This could amount to a significant amount of Javascript loaded on your site. Do you really need all address metadata for all countries on the initial load?
All address metadata for all countries can easily top 1MB and that introduces a significant bottleneck for users on slow networks and/or slow devices.
Users will most likely be dealing with one country, and if it's needed to input an address in a different country, users can switch the country via dropdown. Why not load the extra metadata when the user changes the country?
To cut down the amount of Javascript sent down to the client in this example, we could dynamically load the address metadata, for only the individual country that is needed when the user selects a different country from a dropdown that is used.
This example is applicable to many areas of modern web applications, such as assets or wherever extra weight can be cut from the initial page load. The extra load time which occurs at a subsequent page can more often than not justify the benefit towards our initial Time to Interactive.
What else?
Fortunately for us, there are many things that can be done in the area of application performance. Some others which might benefit you and your team:
Parallel requests
Take advantage of browser parallel request capabilities. A blocking request is a browser AJAX request that blocks other network requests from occurring when the browser may support up to 8 simultaneous. If you perform more than one network request from your app during the initial load, make them parallel and non-blocking.
Maintaining improvements
We all know that maintaining application performance can be a challenge when there are many contributors to a code base. Fortunately there is one simple way to regulate the size of our base bundles:
Block builds!
// webpack.config.js
performance: {
assetFilter: (assetFilename) => (/\.(png|jpg|gif|svg)$/).test(assetFilename),
maxAssetSize: 20000,
hints: process.env.NODE_ENV === 'production' ? 'error' : 'warning',
maxEntrypointSize: 65000
}
Google Lighthouse audits
https://developers.google.com/web/tools/lighthouse
Audit your code and pull requests to mainitain your applications performance. By far one of the best tools to test and maintain your application's performance.