Web Application Performance Checklist
Performance is crucial in today's web applications. A slow app feels buggy to the users and make them flee it.
Although performance is such an important topic, there are so many optimization vectors that it's easy to forget some of them. The intent of this checklist is to gather performance practices to apply when developing a web application.
Contributions and stars are welcome
While you apply these practices, you should always keep the following rules in mind:
- Don't optimize too early
- Don't let performance ruin productivity
- Always check an optimization is efficient by measuring performance before and after it
Table of Contents
- Set your objectives
- Think about performance when building your tech stack
- Network and Infrastructure
- Specialized checklists
Set your objectives
- Define the performance metrics and objectives that are important to your business
- The RAIL model is generally a good model to start with.
- If speed is an advantage you want to have against competitors, know that users usually will feel you are faster if you are at least 20% faster than them.
- Plan out a loading sequence; this way you can define early what is really important in your content, what to load first and what to load later
- Make a performance budget
- The performance budget calculator is useful to estimate your budget depending on the performance you want to obtain. This one is nice too.
- Remember that this budget takes compression in account.
- Currently the recommended budget is max. 170kb gzipped, but it really depends mostly on your user-centric objectives
Think about performance when building your tech stack
Before beginning to build your app, you should have some preparation in order to ease your work on performance later.
- If choosing between SPA frameworks, take in account features like server side rendering; these features will be hard to add later
- Take in account how much every library / framework will take on your performance budget; don't use too much of them
- Bundlephobia can help you estimate the size of a new dependency.
- Make sure you need custom fonts before using them
- This article can help you
- Consider technologies like AMP and Instant Articles, but be aware of their pros and cons
- Keep also in mind these solutions are not mandatory to obtain correct performances.
- When choosing a web framework / library / language, take in account the following points:
- How fast is the library / underlying language (but be aware that benchmarks are usually biased)?
- How easy will it be to handle concurrency?
- Does it allow an efficient resources management, e.g. using a connections pool or an event loop?
- Make sure you use the right DBMS for your needs
- Usually a relational database will cover most needs, but in some cases a NoSQL database may be a better fit.
- If hesitating between NoSQL solutions, this comparison may help you.
Images represent in average ~60% of a page's weight, thus it's an important part to optimize.
- Use WebP compression format for browsers that accept it
- Use responsive images with
- Optimize manually important images or script their optimization
- Lazy load images
- Replacing animated gifs by videos can reduce their size dramatically (details here)
Reduce code size
- Minimize the source code
- Use tree shaking (e.g. with webpack) to remove unused code
- If your bundled code file is too big, use code splitting to load only what's needed first and lazy load the rest
- Serving ES2015 code to browsers supporting it and ES5 code to browsers that don't, using
nomodule, can improve the bundle size and parsing time (details here)
Reduce number of requests
- Replace third parties components (like sharing buttons, maps...) by static components
- Tools like the Simple sharing buttons generator can help you.
- Cache requests client side using service workers
- Bundle common images using CSS sprites
Prepare next requests
You can use prefetching to prepare the browser to next requests and make them faster or even instant. This article is a few old but explains well the following techniques.
- Use dns-prefetch to resolve the domain of services you may need
- Use preconnect to do DNS lookup, TCP handshake and TLS negociation with services you know you will need soon
- Use prefetch to request specific resources that are likely to be needed soon, like images and scripts
- This technique makes an especially good combination with lazy loading.
- Use preload to request specific resources that will be needed in the current page, e.g.
<script>tags at the end of the body
- The difference between
preloadis explained here.
- The difference between
- Use prerender to request and prerender pages that are very likely to be visited soon, like homepage or user dashboard
- Be aware that this technique is quite heavy, make sure you know what you are doing before applying it.
Optimize time to rendering
Ideally the critical code should fit in 14KB in order to be server in the first TCP roundtrip (why 14KB?). These techniques help to achieve this goal.
- Inline critical CSS in the
<head>of your pages
- If critical CSS is inlined, you can then defer the CSS files loading
- loadCSS can help you achieving that
- When the CSS files are loaded, set a cookie to avoid using inlined critical CSS anymore; this way you will benefit from browser cache
- More explanations on this technique here
- Defer scripts execution, especially social media buttons and ads
- Defer fonts loading; the technique is explained here
- Use server side rendering if making an SPA
- Subset your fonts using Font Squirrel's font generator
- Use WOFF2 with fallback to WOFF and OTF
Make animations smooth
- Prefer animating using CSS' transform and opacity; more explanations here
- If animating with JS, use requestAnimationFrame instead of
- Avoid animating during high network activity; for example wait your page is fully loaded
User perceived performance
User perceived performance is often disregarded but can be more important than actual performance. These techniques allow to improve it.
- You should use a loader only on "long" / "heavy" tasks, i.e. tasks the user can imagine they are heavy (e.g. account creation)
- Instead you can use animations to illustrate the transitions following user's action, for example transition from a page to another
- Show app shell before content if needed; more explanations here
- If using JPG images, you can use progressive JPGs to improve their loading perception
- If not using especially JPG, you can replace your images by cheaper components until they are loaded
- You can replace an image by a canvas filled with its main color.
- You can replace an image by a very lightweight, blurred version of it. This efficient technique is explained in this article from Facebook.
- You can also simply use a low-quality version of it.
- Make an optimistic UI to make some interactions feel instant; a quick explanation can be found here
- Provide batch queries / transactions instead of making the client send multiple requests
- Identify & optimize slow resources
- Parallelize slow tasks
- Use relevant data structures
- Don't overuse serialization
- Generate static content when deploying so that it will be computed only once
- If possible, use jemalloc to improve memory allocation
- Use HTTP cache; the different caching techniques are explained in this guide
- Consider using ESI if your app is not a SPA
- Cache calls to other services using Redis, a reverse proxy...
- Cache data slow to compute and memoize slow functions
Don’t loose time with non urgent tasks
- Defer tasks to workers using a queue or use event based patterns like Event Sourcing
- Use UDP for immediate but not vital tasks like logging
Don’t loose time with errors
- Fail fast by validating request inputs as soon as possible
- Use the circuit breaker pattern to avoid waiting timeout when needing another service
- On sensible resources, detect suspicious requests as attacks before actually handling them; attacks can cause heavy resources consumption
- For example, detect a login request as part of a brute force attack before fetching user data from the database.
- First, use indexes smartly
- Tune the DB; tools like MySQLTuner and the experimental OtterTune can help you
- Identify & optimize critical and slow queries (e.g. code that produces n+1 queries)
- In most SQL databases,
EXPLAINcan help by showing the execution plan for a query
- In PostgreSQL,
EXPLAIN ANALYZEcan help further by executing the explained query
- In most SQL databases,
- If using pagination, use last row instead of
offsetas a starting point; more explanations here
- Once you are sure the used DBMS is the good one for your needs, take advantage of its advanced features (e.g. materialized views in Oracle, hyperloglogs in Redis...)
- Don’t use ORM for complex queries, unless you know what you’re doing
- If possible, defer heavy tasks to moments of the day where there is less load on the database (at night for example) to save resources when needed
- If possible, enable jemalloc to improve memory allocation
- If using UUIDs, reorder them before storing them; more explanations here
- Try different storage engines
- On many DBMSs RocksDB often gives interesting results.
Network and Infrastructure
- Serve static content using a CDN to shorten the distance between the client and the server
- When using your CDN take into consideration features like HTTP/2 support, compression...
- Deploy your app on several datacenters, also to shorten the distance between the client and the server
- Serve resources compressed using Brotli if it's supported, Gzip otherwise
- Compress resources that are rarely changed using Zopfli
- Use HTTP/2 and its features like server push and enable HPACK to compress HTTP headers
- Use OCSP stapling to fasten TLS shaking
- Use 0-RTT resumption to avoid round trips during TLS negotiations
- Avoid redirects as they increase the number of needed requests
- If using a microservices architecture, bring services needing each other often closer, ideally in the same machine
- Kubernetes' pods can help achieve this goal
- Measure server side performance; this is usually already done by the web framework
- Measure client side performance; tools like Web Page Test can help you
- Measure client side performance by country; results may hugely differ from one to another
- Use tools like Lighthouse to audit your site
- Load test your servers as they probably won't have same perfs under 10rps and 1000rps
- Keep track of queries to the databases to ease slow queries discovery
- Keep your dependencies up-to-date as their performance is often improved by their maintainers
- Take in account the
Save-Datarequest header to serve lighter assets to clients with limited resources