React Performance & Optimization
What You'll Learn
- Loadtime performance vs Runtime performance
- How to measure and improve both (loadtime and runtime)
- React-specific ways to optimize your project/codebase
- How to make performance work toward the best UX
Performance in React apps can be thought of in 2 ways:
- Loading Performance - Compressing & loading code/assets (mostly Non-React things)
- Runtime Performance - CPU & rendering issues (mostly React-specific things)
Loading vs. Runtime
Loading Performance - This is a measure of how fast the content is loaded when a user visits your webpage. Some specific metrics are First Contentful Paint (FCP), Largest Contentful Paint (LCP), First Input Delay (FID), TTI (Time to Interactive), and maybe a "Speed Index".
Runtime Performance - This is a measure of how "smooth" your application runs and functions after the initial load. Some specific metrics for this might be "frame rate", "CPU", and "Memory Usage".
Measuring Loadtimes
Optimizing Loadtimes
Send as little code/media as possible over the network, and optimize everything
- Utilize GZip compression server-side to compress all in-flight HTTP requests
- Optimize all images and videos included in the bundle
- Lazy load images
- Building and Minifying all project assets to CSS / JavaScript
- Various Methods of Code Splitting
- Server-Side Rendering (SSR) and Static Site Generation (SSG) can improve the First Contentful Paint and Time to Interactive metrics, as they allow the browser to render the page more quickly (and are accessible by search engines).
- Adding SSR / SSG to an existing project is no small feat and will take you more than an afternoon. Try to evaluate early on if your application would benefit from these technologies so you can configure your project the right way from the start.
Measuring Runtimes
Modern React is pretty fast by default. Unless you're building complex components/features - you don't need to reach for optimization tactics until you notice a component/feature behaving slowly. The best advice to follow:
- Don't optimize too soon
- Know what to measure
In modern React apps, most of the performance issues you'll run into can probably be simplified down to rendering problems (either too slow, or too much). The visual in the tweet below explains how rendering can cause cascading effects across a large application.
Other helpful resources for understanding how to measure runtime performance in React apps:
- Measuring Performance in Chrome
- Debugging in the Browser
- Devtools Coverage - shows how much of your code is being shipped but then NOT running (while session is recording)
- Use the Profiler to measure rendering performance of a React tree programatically
- How to Detect Slow Renders in React?
- How many re-renders are too many?
Optimizing Runtimes
Runtime performance issues usually boil down to two types of issues:
-
Fixing Slow Renders
- Fix the slow render before you fix the re-render
- Understand that ES6 is single-threaded, therefore laggy behavior can happen easily
- Offload expensive work that shouldn't block rendering to Web Workers
-
Fixing Profuse Re-Renders
- Use Refs for state & values that shouldn't cause re-renders (or that aren't rendered at all)
- Be careful of setting state in an effect or outside of event-handlers / conditions
Memoization & Virtualization can solve slow renders or profuse re-renders in different ways:
- use memo to memoize components and cut down pointless re-renders of child components when their props aren't changing
- You should probably use
memo()
around any component immediately under Context Provider - See
memo()
in action with a visual example
- You should probably use
- use useMemo() to memoize specific work within your components, cutting down the render time by skipping it if the recalculation isn't needed
- use useCallback() to memoize (cache) functions that are created for use in your components, cutting down the render time by skipping it if the function doesn't need to be recreated
- use virtualization for efficiently rendering large lists of datasets, cutting down render time
- A library called Million.js has been making the rounds lately, apparently able to replace the VDOM in React with a version that's up to 70% faster.
UX Performance Best Practices
- Consider throttling/debouncing frequent updates in the DOM. Humans can't see/process more than a few subtle updates in 1 second (1000 milliseconds), so there's no computational need to update something 100's of times per second.
- Read more about how fast of an interaction is too fast for humans
- For actual changes in the UI/data that's being shown, try not to show more than 4-5 updates per second. Any more, and the humans using your app won't even be able to tell the difference between each update, making it pointless.
- RxJS has traditionally been a great resource for throttling data throughput in a JS application
- Treat loading/pending states that are active for < 400ms uniquely, you should do either:
- Simply don't show the loading state, as "delay" in the app that's < 400ms probably won't be perceivable by the users anyways
- If you show the loading/pending state - require a minimum of ~400ms before switching back to a non-loading state
Typical Performance Hangups in React
- Pitfalls of
Context
anduseContext
- Using SVGs in JSX can sometimes result in bloated bundles
Misc. Articles
More resources about optimizing JavaScript/React performance
- Improving React Interaction Times by 4x
- Improve the Performance of your React Forms
- One Simple Trick to Optimize React Re-Renders
- Speeding up the JavaScript ecosystem - one library at a time
- Making Tanstack Table 1000x faster with a 1 line change
- Optimizing React performance without refs and memo
- React Performance Checklist - Cory House