coryhouse / coryhouse/reactjsconsulting
Performance Checklist
- Dominant language
- JavaScript
- Stars
- 374
- Forks
- 33
- PR merge metrics
- No merged PRs in 30d
Description
# 42+ ways to make your React app faster ⚛️:
## Performance Testing
- [ ] Check [core web vitals like INP](https://twitter.com/addyosmani/status/1767540607046869089)
- [ ] Use https://million.dev/lint
- [ ] Analyze performance via the React dev tools.
- [ ] Use React dev tools "Highlight Updates" to see what's re-rendering. Then be strategic.
- Ask: why is this component re-rendering?
- Ask: Could I avoid passing the prop that's causing it to re-render? Could it access this data via Context instead to avoid passing it through intermediate components that don't need it?
- [ ] Enable [why each component is rendering](https://medium.com/inato/prevent-re-renders-in-your-react-app-using-react-profiler-93c492110e30).
- [ ] [Learn to use the React profiler, and read the flamechart](https://medium.com/inato/prevent-re-renders-in-your-react-app-using-react-profiler-93c492110e30): Yellow = took more time. Blue = took less time. Gray = did not render during this commit. ([source](https://reactjs.org/blog/2018/09/10/introducing-the-react-profiler.html#flame-chart))
- [ ] Use `console.time` like this, against the prod build, to measure slowness and compare to a before and after to determine if `useMemo` is a net win: `console.time('filter array'); const visibleTodos = getFilteredTodos(todos, filter); console.timeEnd('filter array');`
- [ ] Use https://github.com/welldone-software/why-did-you-render
- [ ] Use Chrome's CPU and network slowdown features
- [ ] Tip: [Store large mock datasets/config objects in JSON. It's parsed 80% faster](https://tomdale.net/2017/07/adventures-in-microbenchmarking/).
- [ ] Create/simulate a large dataset/slow component using one of the options below
Generate a simple fake dataset in a loop.
```js
export function generateProducts() {
const products = [];
for (let i = 0; i < 10000; i++) {
products.push(`Product ${i+1}`);
}
return products;
}
```
Generate a large fake dataset using tools like Faker, Chance, etc.
```js
import faker from "faker";
export const fakeNames = Array.from(Array(10000), () => {
return faker.name.findName();
});
```
Simulate a slow component via a loop:
```jsx
function ExpensiveTree() {
let now = performance.now();
while (performance.now() - now < 100) {
// Artificial delay -- do nothing for 100ms
}
return
I am a very slow component tree.
;}
```
## Forms
- [ ] [Split long forms into separate steps](https://twitter.com/housecor/status/1655915312972267527)
- [ ] Consider [uncontrolled components](https://reactjs.org/docs/uncontrolled-components.html) for large, expensive forms. In other words, [Handle state in each input](https://epicreact.dev/improve-the-performance-of-your-react-forms/). Here is [my approach](https://codesandbox.io/s/fast-react-form-with-internal-field-state-xd3dcn?file=/src/App.tsx) which was loosely inspired by [Kent's Fast forms blog post](https://epicreact.dev/improve-the-performance-of-your-react-forms/) - he provides an (unfortunately contrived, incomplete, and buggy) example, but the core idea is sound. For uncontrolled forms, also consider via [react-hookform](https://react-hook-form.com/)
## HTTP
- [ ] Cache HTTP requests via react-query/swr/Apollo/etc. [react-query also simplifies code greatly](https://twitter.com/housecor/status/1437765709849927684)
- [ ] Lift state so calls aren't repeated
- [ ] Use Promise.all to make calls in parallel
- [ ] Lazy load content below the fold, using tools like [useOnScreen](https://usehooks.com/useOnScreen/)
- [ ] Streamline HTTP requests - fetch only necessary data
- [ ] https://csswizardry.com/2023/10/the-three-c-concatenate-compress-cache/
- [ ] [Use setQueryData to pre-populate the detail page cache. Now the detail page loads immediately.](https://twitter.com/housecor/status/1757823190855004506)
## Rendering
- [ ] Statically render if possible (Easy via Next.js, Gatsby)
- [ ] [Lift fetches to common parent ](https://twitter.com/housecor/status/1531642615283625984) or fetch in parallel via tools consider server rendering to avoid slow network waterfalls (easy via Remix / Next.js)
- [ ] Prefetch / render-as-you-fetch (React's "default" is fetch as you render since the render triggers a fetch via `useEffect`). React-query's [prefetching](https://react-query.tanstack.com/guides/prefetching) is a simple way to render as you fetch. Remix also does this by default since nested routes declare data dependencies and run in parallel on the server. [My tweet on this](https://twitter.com/housecor/status/1531642615283625984)
- [ ] If server rendering with Remix, Next, Docusaurus, etc, [Enable time-slicing with React.startTransition at the root](https://twitter.com/ryanflorence/status/1547959632663961602). This avoids jank if the user scrolls before hydration completes.
- [ ] Consider using [million's block virtual DOM instead]( https://million.dev/blog/virtual-dom) if there is a lot of static content with little dynamic content. Million diffs state instead of DOM, which is fast if there's a little state, but a LOT of DOM.
## Context
- [ ] Minimize context usage - Consider [component composition](https://www.youtube.com/watch?v=3XaXKiXtNjw&t=5s) instead (composing components that accept children, so props "Lifted" / composed at a higher level in the tree). Dan calls this [lift content up](https://overreacted.io/before-you-memo/#solution-2-lift-content-up).
- [ ] Avoid putting data that changes a lot in context
- [ ] Separate contexts based on when they change
- [ ] Place context providers as low as possible
- [ ] [Consider splitting the state and dispatch into separate contexts](https://kentcdodds.com/blog/how-to-optimize-your-context-value)
- [ ] [Wrap the component directly under your context provider](https://twitter.com/sophiebits/status/1228942768543686656) in React.memo or use {props.children} - This means changes to the context value will rerender only the components consuming the context instead of the entire subtree. More [here](https://blog.isquaredsoftware.com/2020/05/blogged-answers-a-mostly-complete-guide-to-react-rendering-behavior). Here's a [codesandbox](https://codesandbox.io/s/laughing-knuth-wu9tm?file=/src/App.js) showing how to memo children. And here's a [visual example of using memo on the direct child](https://alexsidorenko.com/blog/react-render-context/).
## Routing
- [ ] Implement client-side routing via React Router (or you frameworks built-in alternative, such as Next.js)
- [ ] [Read search params via useSearchParams efficiently by passing props](https://x.com/housecor/status/1805634751040045302)
## Keys
- [ ] Assure keys are assigned, and stable over time. Their values should not change based on array order or edits to the data.
- [ ] [Reuse keys](https://twitter.com/vuhminh/status/1337428007112253442) to avoid needless renders for items that frequently appear/disappear in the viewport.
- [ ] Consider using the array’s index as `key` for dynamic lists with stateless items, where items are replaced with the new ones - paginated lists, search and autocomplete results and the like. This will improve the list’s performance because the entire thing doesn't have to re-render. The component instances are reused. [Demo](https://codesandbox.io/s/react-key-attribute-faster-with-index-3mnyxp?file=/src/country-settings/list.tsx) and [blog post](https://www.developerway.com/posts/react-key-attribute)
## State
- [ ] Keep state as local as possible. Start by declaring state in the component that uses it. Lift as needed.
- [ ] Store data that doesn't need to render in refs
- [ ] Consider useReducer over useState so you can pass dispatch down instead of callbacks (avoids needless renders)
- [ ] Avoid deep cloning. To avoid, only clone the subobjects that have changed. Or perhaps better yet, avoid nesting objects in state since doing so can lead to needless renders. Instead, "flatten" state by creating separate pieces of state.
- [ ] [Use memo, ref, or pass a func to useState if the initial value calls an expensive func](https://medium.com/@_m1010j_/how-to-avoid-this-react-hooks-performance-pitfall-28770ad9abe0), otherwise, it will be calculated on each render.
- [ ] Use `useTransition` for low priority updates (reduces jank) - [Demo](https://codesandbox.io/s/usetransition-and-usedeferredvalue-demo-47ruc8)
- [ ] Use `useLayoutEffect` to avoid a Flash of unstyled content when you need to read and manipulate the DOM before the user sees it - [Demo](https://codesandbox.io/s/competent-dream-yuw9b9?file=/src/App.js)
## Memoization / Identity
- [ ] Use React compiler. Then, it automatically applies `useMemo` and `useCallback`
- [ ] Consider wrapping functions passed to children in useCallback to avoid needless renders in children
- [ ] Prefer pure functions (which can be extracted from the component) over useCallback when possible
- [ ] [Watch for memory leaks with useCallback and closures](https://schiener.io/2024-03-03/react-closures). Note that [the React compiler helps, but may not save you](https://schiener.io/2024-07-07/react-closures-compiler).
## Props
- [ ] Pass the minimal amount of data to each component (remember, components re-render when props change)
- [ ] Pass primitives (strings, numbers...) to child components to assist with diffing. Avoid passing arrow funcs and objects on props when performance is a concern since it leads to needless re-renders of child components.
- [ ] [useDeferredValue](https://codesandbox.io/s/usetransition-and-usedeferredvalue-demo-47ruc8) if it's a low priority update and you can't use `useTransition` because you don't control the value coming in (coming from third party or via a prop you can't control). [Another demo](https://codesandbox.io/s/heavy-update-as-non-urgent-deferred-z8dzg?file=/src/FilterList.js)
## Component composition / Children
- [ ] Put content that renders frequently in a separate component to minimize the amount that's rendered
- [ ] Embrace reusable components. Each reuse of a reusable component is nearly free.
- [ ] Compose higher level components out of lower level components.
- [ ] Create layout components to centralize reusable layout
- [ ] [Lift content up](https://overreacted.io/before-you-memo/) - Pass children down (do this before you memo). Why does this work? Because components passed as `children` don’t re-render since they are just props. [Note: don't pass a function as a child, since it'll still re-render because the func will be recreated on each render](https://www.developerway.com/posts/react-elements-children-parents)
- [ ] Declare functions that need not be in the React component outside the component. This way they're not reallocated on every render.
- [ ] Declare static values outside the component so they're not reallocated on every render.
## Design
- [ ] Use pagination, sorting, filtering, pages, tabs, accordions, modals, etc to avoid displaying too much data at the same time.
- [ ] Use tools like [react-window](https://github.com/bvaughn/react-window) to handle large lists by only rendering what's currently visible.
- [ ] Consider implementing optimistic updates when the API is slow (immediately update the UI even though the API call is still in progress behind the scenes)
## Bundle optimization
- [ ] Split the bundle via React.lazy or use [loadable-components](https://loadable-components.com/). Not merely pages. Consider splitting components too. Remember that Next.js automatically bundle splits. When lazy loading in React Router, put the loader in a different file than the lazy loaded component, or [the bundle split won't work](https://reacttraining.com/blog/spa-lazy-loading-pitfalls).
- [ ] Consider [prefetching lazy loaded components](https://twitter.com/housecor/status/1200554167644966912) that are highly likely to be used
- [ ] [Eager load lazy routes on link hover](https://blog.maximeheckel.com/posts/preloading-views-with-react)
- [ ] Embrace reusable logic via custom hooks and pure utility functions
- [ ] Avoid large utility libs like lodash, underscore, Ramda. [Plain JS is likely all you need](https://twitter.com/housecor/status/1437765729684701190). [Avoid these libs you probably don't need](https://twitter.com/HenrikJoreteg/status/1032879748387459072) Use ESLint to [forbid large/redundant imports](https://eslint.org/docs/rules/no-restricted-imports)
- [ ] [Other things that likely shouldn't be in the bundle](https://twitter.com/HenrikJoreteg/status/1032879748387459072) - Programmatically [enforce via ESlint](https://eslint.org/docs/rules/no-restricted-imports).
- [ ] [Store large config objects in JSON. It's parsed 80% faster.](https://tomdale.net/2017/07/adventures-in-microbenchmarking/)
- [ ] Check what's in your bundle via webpack-bundle-analyzer
## Third party libraries
- [ ] Avoid named imports when importing third party libraries / components (doing so can bloat the bundle by importing the entire lib) For example, avoid `import { Tab } from "x"`. Prefer `import Tab from "x/Tab"` when possible.
- [ ] Prefer native HTML inputs over fancy components that simulate native behaviors
- [ ] Use [Partytown](https://partytown.builder.io/) to load heavy third party libraries via a separate web worker thread
## Styling
- [ ] Consider Tailwind to minimize style bundle size
- [ ] Consider CSS modules over CSS-in-JS to avoid a runtime
## Framework
- [ ] Consider Gatsby or Astrobuild to compile components into static HTML
- [ ] Consider Next.js, Remix, Redwood to server render
## More examples
https://piyushsinha.tech/optimizing-performance-in-react-apps-i
https://piyushsinha.tech/optimizing-performance-in-react-apps-ii
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.