Part 3 of our React Performance series
Welcome to the React Performance Optimization series on systemseed.com. It’s a practical guide to improving performance of mature or even legacy React websites without significant refactoring.
Catch up on the previous sections:
- Part 2: 7 essential tools for web performance audits
Optimize dependencies for performance
First, review your production build with a specialized tool like Webpack Bundle Analyzer.
Beware though, reducing the bundle size doesn't automatically improve Web Vitals. You need to audit the entire application after each improvement suggested below. See Part 2 to learn more about performance audit tools.
2. Remove duplicates
The first issue you may notice is that multiple versions of the same library are included in the production bundle. For example, your app can contain two different React versions. This is a huge overhead for a client-side application.
You can resolve this issue by carefully updating dependent packages or forcing the appropriate version in resolutions.
3. Apply performance updates
It's not always possible to keep all dependencies up to date, but older versions can become inefficient as technologies evolve.
Review your largest dependencies and check whether their newer versions introduce performance optimizations. Check their release notes and compare bundle sizes on Bundlephobia. If the library is no longer supported, replace it with an alternative.
4. Replace with alternative
The classic example of this optimization technique is the moment.js library. Go to Budlephobia again and check out similar packages, e.g. date-fns. Of course, size shouldn't be the only criterion for your choice, but it should always be considered.
5. Remove a dependency
In each individual case, you need to follow common sense, and often the optimal solution would be to use a well-tested external library rather than maintaining your own code.
6. Think for the long term
It's great to see a performance boost after a one-off optimization, but it's equally important to maintain this result in the long run. The following recommendations can help build a good dependency management strategy:
Don't add dependencies before you need them
It's tempting to create a boilerplate with all the “useful” libraries pre-installed - don't do that with client side applications.
Share dependency ownership with the entire development team
Every developer on the team should be comfortable managing and using application dependencies.
Embrace the boring technology
Keep dependencies (reasonably) up to date
Very frequent updates may be overwhelming, but outdated packages can slow down developers and introduce bugs. Find your balance. Tools like Dependabot or Snyk can help automate the process.
Optimize custom code
Poor performance is effectively a bug as it requires debugging, fixing and preventing in the future. There are many performance tools out there, but unfortunately, none of them will tell you how to fix your code. To catch a performance issue, apply the same techniques as for bug hunting. The following 6 steps will help you go through the process.
It's tempting to postpone performance work due to a lack of “expert knowledge”. The truth is that this kind of knowledge is obtained and maintained through practice. You already have all the necessary skills to get started. Be prepared to take a lot of notes and run a lot of experiments. Eventually, this will become your expert knowledge. A good understanding of your own codebase will help too.
2. Define steps to reproduce
Just like with any other bug, you need concrete steps to reproduce it. You can use Web Vitals or other measurables, e.g. First Contentful Paint for the mobile home page, time to render the results of a search query, memory consumption after using the app for 10 min, etc. Know what you are trying to improve.
3. Localize the suspicious code
Now is the right time to take a good look at the Chrome and React profilers mentioned in Part 2. They are best used in combination with the detailed steps to reproduce: start the profiler, follow the steps, stop the profiler, review, document, repeat.
If you have never used the profilers before, take your time to learn the interface. In the next part we will debug a test application to get familiar with both tools. In the meantime you are welcome to check-out their documentation:
Don’t fix anything yet! Our goal at this stage is to understand how the problematic part of the application works and come up with a list of potential contributors to the performance issue identified. If there are no obvious candidates for fixing, then just pick any one. You need to start somewhere.
4. Confirm and prioritize your findings
The easiest way to measure the performance cost of the suspicious piece of code is to remove or to mock it, re-run the performance checks and compare the results. For example, you can completely comment-out an event handler or replace an API request with a mock object.
Repeat the same process for each performance optimization candidate you have identified, and carefully note down your results. Then, prioritize your findings considering the potential savings and complexity.
What if you didn't find anything significant? You are looking in the wrong place. Start again from your steps to reproduce, try another profiler or logging mechanism, throttle network & CPU to make issues more visible.
5. Implement the fixes
In real life, there will be multiple issues that have to be fixed, but let's imagine we identified only one significant performance issue. We know how to reproduce it, we found the code that creates it and we know what to measure to validate the fix. So, we can finally fix it!
First of all, look at the problematic piece of code and compare it with the framework documentation. Does the code violate any known best practices? Are some methods or patterns in your code known for bad performance? Many bugs are caused by typos, uncareful coding or API misuse.
Finally, there are cases when it's not possible to “fix” the issue. You can work on the UX to make “waiting” easier for the user, you can wait for the industry to come up with the fix, or at the very least you can make the entire team aware of the issue - eventually, you as a team will find a better solution.
6. Think for the long term
Avoid premature optimization
Always measure potential savings before jumping into non-obvious optimizations.
Integrate community best practices in your linting
Preconfigured ESLint rule sets (1, 2, 3, 4) accumulate wisdom of hundreds of experienced developers. Googling an ESLint error that prevents me from committing always teaches me something new.
Optimize your production build
Employ tools like Webpack, Browserlist, PostCSS, PurgeCSS to minify and optimize your code automatically.
Performance work becomes much easier with practice. In the next article we will walk through a simple profiling process together.
React performance isn't about useMemo or PureComponent, it's about understanding the framework as a whole and when to use what. Encourage your team to learn about web fundamentals and framework best practices.
And of course, share your feedback about this series. What was unclear or missing? I’d like to know.
This is it for part 3 of the React Performance Optimization series on systemseed.com. Follow us on Twitter to read the next part as it gets published. Next time we will learn how to use Chrome and React profilers.