-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
adds updates to react blog performance
- Loading branch information
Showing
1 changed file
with
119 additions
and
67 deletions.
There are no files selected for viewing
186 changes: 119 additions & 67 deletions
186
content/5-tips-for-optimizing-your-react-apps-performance/index.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,123 +1,175 @@ | ||
--- | ||
title: 5 Tips for Optimizing Your React App’s Performance | ||
date: "2023-01-12" | ||
date: "2024-07-01" | ||
description: "Poor app performance can reduce user engagement and will negatively affect SEO, here are 5 tips to optimize your react app" | ||
cover: "optimize-react-app-performance.png" | ||
category: "featured" | ||
author: "Joel Coutinho" | ||
author: "Connor Peshek" | ||
--- | ||
|
||
## Table of contents | ||
- [Introduction](#introduction) | ||
- [Tip 1: Reduce unnecessary renders](#tip-1-reduce-unnecessary-renders) | ||
- [Tip 2: Lazy loading with code splitting](#tip-2-lazy-loading-with-code-splitting) | ||
- [Tip 3: Debouncing and Throttling](#tip-3-debouncing-and-throttling-event-actions) | ||
- [Tip 4: Virtualize long lists](#tip-4-virtualize-long-lists) | ||
- [Tip 5: Optimizing your images](#tip-5-optimizing-your-images) | ||
- [Conclusion](#conclusion) | ||
## Table of Contents | ||
|
||
## Introduction: | ||
1. [Introduction](#introduction) | ||
2. [Understanding React Performance](#understanding-react-performance) | ||
3. [Performance Optimization Techniques](#performance-optimization-techniques) | ||
4. [Avoid Anonymous Functions in JSX](#avoid-anonymous-functions-in-jsx) | ||
5. [Avoid Excessive Component Re-renders](#avoid-excessive-component-re-renders) | ||
6. [Use Production Build](#use-production-build) | ||
7. [Conclusion](#conclusion) | ||
|
||
One of the most frustrating things as a React developer is investing time and effort into building an application only to find it slow and unresponsive. | ||
|
||
In this post, we’ll go over 5 tips to improve the performance of your React app. | ||
# Importance of Performance Optimization in React Apps | ||
|
||
Optimizing performance in a React app isn’t just about being a good developer, it's important because less optimized pages make less money and rank lower on Google SEO. The internet is competitive. There are thousands of websites competing for everyone’s limited attention spans, and people hate to wait. People hate waiting so much that a report found that a site that loads in 1 second has a conversion rate 3x higher than a site that loads in 5 seconds. That’s a big difference. And around 2020, Google started using the core web vital metrics to factor in your Google ranking from a search result, meaning that less optimized sites rank lower on Google searches. | ||
|
||
## Tip 1: Reduce unnecessary renders | ||
There are 3 main metrics used as part of core web vitals. There's Largest Contentful Paint (LCP), which rates how quickly the first main image or text is shown to the user; Interaction to Next Paint (INP), how responsive your website is to events such as scrolling, clicking, or inputting text; and Cumulative Layout Shift (CLS), which measures how much your website layout changes with different interactions. | ||
|
||
Unnecessary rerenders can slow down your apps making them feel sluggish and unresponsive. | ||
So render time and performance matter, both for retaining customers and ranking on Google. But what can we do about it? There are lots of options for optimizing a web application, regardless of it being made with React. We can use a CDN, compress our images and lazy load them underneath the fold (lazy loading above the fold can hurt LCP), make our API calls as efficient as possible, remove unnecessary dependencies, and make sure we build our project in production mode in our bundler of choice. But what are things more specific to React that we can do? | ||
|
||
By leveraging some of React's built-in hooks, we can keep unnecessary renders at a minimum. | ||
In order to break down how to make our React code more efficient, let's first talk about how React works with the React Virtual DOM. | ||
|
||
## How does React’s Virtual DOM work? | ||
|
||
### React.Memo: | ||
The React Virtual DOM works by listening for state changes, comparing the DOM for any differences in its own virtual DOM, and then rendering any components that have had state changes, along with that component's children. Since it doesn't have to re-render the whole DOM, it makes rendering changes much faster. But there are lots of ways to accidentally write inefficient code in React, and there are lots of more advanced ways to write React that can massively optimize performance. Let’s walk through 11 different ways to optimize your React app. | ||
|
||
In React, components will re-render when state or prop values change. This also applies to child components where props might remain the same but the parent component’s props change. | ||
## 11 React Performance Optimization Techniques | ||
|
||
In this case, we can use `React.memo()`, a higher-order component to cache the output of the child component. Now the child component will be rendered only when its props change. This can be very useful for components that have many props that do not change frequently. | ||
### Measure React Performance | ||
|
||
React.profiler is a higher order component that can measure the performance of its child components, and the React DevTools allow you to see the React app’s performance directly inside your web browser. | ||
|
||
```tsx | ||
const myComponent = React.memo((props) => { | ||
/* render using props */ | ||
}); | ||
It’s always good to know where your performance bottlenecks are when you want to start improving your application. You can do this programmatically by using the Profiler component. You give the Profiler component a callback in the `onRender` prop and can check render performance. For example, the following code would get render information on your whole React application: | ||
|
||
export default myComponent; | ||
``` | ||
|
||
You can find the React documentation on how to use React memo [here](https://beta.reactjs.org/reference/react/memo). | ||
|
||
### useCallback: | ||
```jsx | ||
// src/index.js | ||
import React, { Profiler } from 'react'; | ||
import ReactDOM from 'react-dom'; | ||
import App from './App'; | ||
import './index.css'; | ||
|
||
`useCallback` will return a memo-ized version of the callback that only changes if one of the dependencies has changed. This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders. | ||
const onRenderCallback = ( | ||
id, // the "id" prop of the Profiler tree that has just committed | ||
phase, // either "mount" (if the tree just mounted) or "update" (if it re-rendered) | ||
actualDuration, // time spent rendering the committed update | ||
baseDuration, // estimated time to render the entire subtree without memoization | ||
startTime, // when React began rendering this update | ||
commitTime, // when React committed this update | ||
interactions // the Set of interactions belonging to this update | ||
) => { | ||
console.log('Profiler ID:', id); | ||
console.log('Phase:', phase); | ||
console.log('Actual Duration:', actualDuration); | ||
console.log('Base Duration:', baseDuration); | ||
console.log('Start Time:', startTime); | ||
console.log('Commit Time:', commitTime); | ||
console.log('Interactions:', interactions); | ||
}; | ||
|
||
```tsx | ||
const memoizedCallback = useCallback( | ||
() => { | ||
doSomething(a, b); | ||
}, | ||
[a, b], | ||
ReactDOM.render( | ||
<Profiler id="App" onRender={onRenderCallback}> | ||
<App /> | ||
</Profiler>, | ||
document.getElementById('root') | ||
); | ||
``` | ||
|
||
## Tip 2: Lazy loading with code splitting | ||
|
||
As your app grows larger with third-party libraries and more functionality, so will your app's bundle. This, in turn, will increase the load time of your app. | ||
|
||
To combat this, we can split the bundle and only load what is currently needed. In React, this can be done by dynamically importing components with the React.lazy function. | ||
You can also use the React Developer Tools in your web browser by installing the plugin from your browser’s extension marketplace - such as the Chrome Web Store - and opening your browser's developer tools. Two React tabs will be added to your developer console - components and profiler. The components tab can tell you details like props of rendered React components, and the profiler tab will allow you to record a rendering timeline of your React application. Simply click the record button and trigger a render, or click the refresh button to record all rendering times from a new page refresh. This can help you get a baseline of what components are rendering quickly and which ones could be causing performance bottlenecks. You can also check things like why a component is re-rendering and use some of the tips later in the article to correct it. | ||
|
||
Before: | ||
### Code splitting and lazy loading | ||
|
||
`import OtherComponent from './OtherComponent';` | ||
Code splitting allows you to break your front-end react bundle into smaller chunks that can be downloaded together in parallel or at the time they are needed, and lazy loading allows you to load less important parts of an application later, improving application performance. These combined can help increase initial load times for an application, affecting our LCP metric. | ||
|
||
After: | ||
By default, reactjs sends one .js file - usually bundled together by a bundler like webpack - which includes all of your react application and runs it in the client’s browser. This is fine if your app is small, but as your app grows in size, this becomes a bottleneck. Also, some pieces of your app are more important to load first. Getting the user the ProductSearch component on an e-commerce page is a much higher priority than getting them the admin page 99% of users will never see. | ||
|
||
`const OtherComponent = React.lazy(() => import('./OtherComponent'));` | ||
You can set up lazy loading in React using React.lazy and React.suspense. When using lazy and suspense, react will automatically code split the components that will be lazy loaded and grab them when they’re needed. This keeps the main bundle small and makes your initial render much quicker. But be sure to not lazy load anything above the fold, as that can actually drop your LCP score. | ||
|
||
This will automatically load the bundle containing the `OtherComponent` when this component is first rendered. | ||
```typescript | ||
// src/index.js | ||
import ReactDOM from 'react-dom/client'; | ||
import React, { Suspense, lazy, useState } from 'react'; | ||
import './App.css'; | ||
|
||
The lazy component should then be rendered inside a `Suspense` component, which allows us to show some fallback content (such as a loading indicator) while we’re waiting for the lazy component to load. | ||
// this component will be removed from the bundle and fetched when needed | ||
const MyComponent = lazy(() => import('./MyComponent')); | ||
// this component is also removed from the bundle and fetched when needed. | ||
// This results in 3 separate .js files | ||
const MyOtherComponent = lazy(() => import('./MyOtherComponent')); | ||
|
||
```tsx | ||
import React, { Suspense } from 'react'; | ||
const App = () => { | ||
|
||
const OtherComponent = React.lazy(() => import('./OtherComponent')); | ||
|
||
function MyComponent() { | ||
return ( | ||
<div> | ||
<div className="App"> | ||
<Suspense fallback={<div>Loading...</div>}> | ||
<OtherComponent /> | ||
<MyComponent /> | ||
<MyOtherComponent /> | ||
</Suspense> | ||
</div> | ||
); | ||
} | ||
}; | ||
|
||
const root = ReactDOM.createRoot(document.getElementById('root')); | ||
root.render(<App />); | ||
``` | ||
You can find the complete docs on code splitting [here](https://reactjs.org/docs/code-splitting.html). | ||
|
||
### Implement React server-side rendering (SSR) | ||
|
||
Server-side rendering is when the page is rendered on the server and then sent over to the client instead of sending a bundle that is rendered client-side. The client’s cpu is not usually a bottleneck for a react application, but bandwidth and latency between the client and server commonly are. By rendering server-side, we can grab all of the data we need from the database before rendering our page, which removes extra trips to the server and can speed up rendering. | ||
|
||
## Tip 3: Debouncing and Throttling Event Actions | ||
[Nextjs](https://nextjs.org/), made by the same people who [made the Vercel deployment platform](https://supertokens.com/blog/how-to-deploy-supertokens-with-react-nodejs-express-on-vercel), is the most popular way to implement SSR in React, and it’s very simple to use. You can simply run | ||
|
||
```bash | ||
npx create-next-app@latest your-app-name | ||
``` | ||
|
||
Debouncing is a technique used to improve the performance of frequently executed actions by delaying them, grouping them, and only executing the last call. For example, if you have a search field where results are queried as you are typing, making API calls for each keystroke would be extremely inefficient since we only care about the final result. In this case, a debounce function would be extremely useful, essentially only calling the search API after a pre-determined timeout. | ||
|
||
to generate a nextjs project. Everything will render server-side by default, with the option to render specific components client-side if necessary. | ||
|
||
Throttling or rate limiting is a technique used to improve the performance of frequently executed actions, by limiting the rate of execution. It is similar to debouncing, except it guarantees the regular execution of an action. This is most useful for high-frequency event actions like resizing and scrolling which cause React components to re-render. Since we don't need to keep these actions 100% in sync we can call the handlers of these events intermittently. | ||
### Virtualize lists | ||
|
||
You can learn more about throttling and debouncing components [here](https://codefrontend.com/debounce-throttle-js-react) | ||
List virtualization, also known as windowing, is when you only render the part of a list that is visible to the user. This helps reduce DOM nodes on a page and make scrolling smoother, which can increase your INP score. | ||
|
||
## Tip 4: Virtualize long lists | ||
Let’s say you have an e-commerce store and someone does a search for "The". I don't know why they would do that, but it's the most common word in the English language, meaning you’ll probably have to render a pretty long list of results. If we virtualize the list instead of rendering all thousand entries, we can cut down on the render time and prevent freezing while scrolling. | ||
|
||
If you need to display a large table or list that contains many rows, loading every single item on the list can significantly affect performance. | ||
There are many libraries, such as react-virtualized, react-window (an updated, lightweight alternative to react-virtualized made by the same team), and react-virtuoso, that can make implementing virtualized lists simple. | ||
|
||
List virtualization, or "windowing", is the concept of only rendering what is visible to the user. The number of elements that are rendered at first is a small subset of the entire list and the "window" of visible content moves when the user continues to scroll. This improves both the rendering and scrolling performance of the list. | ||
|
||
You can use the [react-window](https://www.npmjs.com/package/react-window) library to start virtualizing lists in your app. | ||
### Properly use React's Keys property when rendering a list | ||
|
||
React requires giving every item in a generated list of jsx a unique key. People tend to use the array index to handle this, but this can cause unnecessary re-renders. Keys are used by react to tell if it needs to re-render items in the list. If you use the array index and then an item is removed or the array is sorted, the keys in the new list will no longer match the old index keys, and all of those items will have to be re-rendered. If we use unique keys, like a userId, we can prevent React from needing to re-render any items in the list when the list’s order changes. | ||
|
||
## Tip 5: Optimizing your images | ||
Good idea | ||
|
||
Although it may seem obvious, large images can significantly impact the performance of your app. From poor load times to sluggish performance, there are clear demerits to non-optimized large images. To avoid these performance penalties, you can compress them, resize them, and serve them in an appropriate format (such as webp). The best way about going about doing this is using an image CDN service which will automate the step of optimizing the images it serves | ||
```js | ||
// index.js | ||
import ReactDOM from 'react-dom/client'; | ||
import React from 'react'; | ||
|
||
## Conclusion: | ||
const App = () => { | ||
const users = [ | ||
{ userId: 1, name: 'Alice', email: '[email protected]' }, | ||
{ userId: 2, name: 'Bob', email: '[email protected]' }, | ||
{ userId: 3, name: 'Charlie', email: '[email protected]' }, | ||
]; | ||
|
||
|
||
return ( | ||
<div className="App"> | ||
<h1>User List</h1> | ||
<ul> | ||
{users.map(user => ( | ||
<li key={user.userId}> | ||
<h3>{user.name}</h3> | ||
<p>{user.email}</p> | ||
</li> | ||
))} | ||
</ul> | ||
|
||
</div> | ||
); | ||
}; | ||
|
||
|
||
Poor app performance can reduce user engagement and negatively affect SEO. By following these tips, you will be able to improve performance with debouncing, throttling and virtualizing long lists, prevent unnecessary re-renders with `React.memo`, and add general improvements to your app's speed by optimizing images. | ||
const root = ReactDOM.createRoot(document.getElementById('root')); | ||
root.render(<App />); | ||
``` |