# React Performance: 10 Optimisation Techniques That Actually Work

By [Eoin Villarin](https://amvprosox.com/about). Published 2026-03-15, updated 2026-10-28. Canonical: https://amvprosox.com/blog/react-performance-optimization-techniques-2025

> Most React performance advice is outdated. These 10 techniques, from the React Compiler and transitions to virtualization and Server Components, cut re-renders and improve INP.

## What React performance optimisation techniques actually work in 2026?

Effective React performance work in 2026 starts with measuring a production build, then enabling React Compiler 1.0 for automatic memoization. After that, colocate state, pass JSX as children, use transitions and Activity for responsive interactions, and ship less JavaScript through lazy code splitting, list virtualization and Server Components. Manual memo becomes an escape hatch.

**Key takeaways**

- React Compiler 1.0 reached stable in October 2025 and works with React 17 and newer.
- Core Web Vitals targets are LCP at most 2.5 seconds, INP at most 200 milliseconds and CLS at most 0.1.
- Colocating state in the smallest component that needs it stops every keystroke re-rendering the whole page.
- useTransition keeps inputs responsive by marking expensive updates, such as filtering 5,000 products, as interruptible work.
- Rendering 2,000 table rows creates tens of thousands of DOM nodes, so virtualize long lists instead of memoizing.

Most **React performance** advice online was written for a React that no longer exists. Wrapping every component in `React.memo` and every function in `useCallback` was the standard tip for years. In 2026, with React Compiler 1.0 stable and React 19.3 shipping transitions, Activity and better profiling tools, a lot of that advice is now noise or even harmful.

Slow React apps still exist, of course. They type with a lag, freeze when a filter changes, and fail Core Web Vitals because one interaction blocks the main thread for half a second. The fixes just look different now: measure first, let the compiler handle routine memoization, fix component structure, and move work off the critical path.

Below are 10 optimisation techniques that actually work in current React, in the order I apply them on client projects, with code you can copy and the situations where each one is worth the effort.

## Why most React performance advice is outdated in 2026

Three changes reshaped the playbook. The React Compiler reached 1.0 in October 2025 and memoizes components automatically at build time. React 19.2 added Performance Tracks to Chrome DevTools and the `<Activity>` component. React 19.3, released in September 2026, made View Transitions stable and lets unrelated transitions render independently.

Here is the full list at a glance:

| # | Technique | Fixes | Use when |
| --- | --- | --- | --- |
| 1 | Measure with Profiler and Performance Tracks | Guesswork | Always, before anything else |
| 2 | Enable React Compiler | Unnecessary re-renders | Most apps on React 17 or newer |
| 3 | Manual memo as an escape hatch | Specific hot spots | The profiler proves a need |
| 4 | Colocate state | Large re-render trees | State lives too high |
| 5 | Pass JSX as children | Wrappers re-rendering content | Stateful layout wrappers |
| 6 | Transitions and deferred values | Laggy input, poor INP | Expensive updates after typing or clicks |
| 7 | Activity for hidden UI | Remount cost, lost state | Tabs, drawers, back navigation |
| 8 | Code splitting with lazy | Large initial bundle | Heavy, rarely used components |
| 9 | List virtualization | Huge DOM size | Hundreds or thousands of rows |
| 10 | Server Components | Client JavaScript cost | Non-interactive, data-heavy UI |

## Technique 1: How to measure React performance before you optimize

Every optimization has a cost in complexity, so prove the problem first. Profile a production build, because development mode adds checks that make everything look slower than it is.

![Detailed graphs on a computer screen, the kind of profile data to study first](https://amvprosox.com/api/img/1793146654805-cum51w)
*Profile production builds before changing any code. Photo: rawpixel, CC0*

- **React DevTools Profiler:** records which components rendered, why, and how long each took.

- **Chrome Performance panel with React Performance Tracks:** since [React 19.2](https://react.dev/blog/2025/10/01/react-19-2), a Scheduler track shows work by priority (blocking versus transition) and a Components track shows which tree React is rendering, right next to long tasks and layout work.

- **Field data:** lab tools miss what real users experience. Collect Core Web Vitals from real sessions, for example with the `web-vitals` library or Chrome UX Report data.

Know your targets. Google's [Core Web Vitals guidance](https://web.dev/articles/vitals) sets "good" at an LCP of 2.5 seconds or less, an INP of 200 milliseconds or less and a CLS of 0.1 or less, measured at the 75th percentile of page loads. For React apps, INP is usually the metric that fails, and my [Interaction to Next Paint guide](https://amvprosox.com/blog/interaction-to-next-paint-inp-guide) covers how to debug it in depth.

## Techniques 2 and 3: Let the compiler memoize, then memoize by hand only where it counts

### Technique 2: Enable the React Compiler

The React Compiler analyzes your components at build time and inserts memoization automatically, including inside conditional logic where hooks cannot go. The [React Compiler 1.0 announcement](https://react.dev/blog/2025/10/07/react-compiler-1) reports that on the Meta Quest Store, initial loads and cross-page navigations improved by up to 12 percent and certain interactions became more than 2.5 times faster, with memory usage staying neutral. It works with React 17 and up.

```
// next.config.ts (after: npm install -D babel-plugin-react-compiler)
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  reactCompiler: true,
}

export default nextConfig
```

Vite, Expo and other setups are covered in the official installation docs. Install the latest `eslint-plugin-react-hooks` too, because its recommended preset now includes compiler-powered rules that flag code the compiler cannot optimize, such as setting state during render.

### Technique 3: Use memo, useMemo and useCallback as escape hatches

With the compiler on, the [React docs on memo](https://react.dev/reference/react/memo) say you typically do not need `React.memo` any more. Manual memoization still has two legitimate uses: keeping a value stable when it is an Effect dependency, and fine-tuning a hot spot the profiler has flagged.

Without the compiler, the old rule still applies: `memo` only helps when a component re-renders often with the same props and its render is expensive. Wrapping a cheap component adds comparison work for nothing. In existing code, leave current memoization in place when you enable the compiler, because removing it can change compilation output, and remove it later with tests.

## Techniques 4 and 5: Fix component structure before reaching for memo

Most unnecessary re-renders come from where state lives, not from missing memoization. Structural fixes are free at runtime and make code easier to follow.

![Red light trail along a road at night, with urgent updates moving first](https://amvprosox.com/api/img/1793146656148-lf7yrw)
*Urgent updates first, expensive renders in the background. Photo: Shusei Toda, CC0*

### Technique 4: Colocate state

When a search box stores its text in a page-level component, every keystroke re-renders the whole page. Move the state into the smallest component that needs it.

```
// Before: typing re-renders ProductGrid on every keystroke
function ShopPage() {
  const [query, setQuery] = useState('')
  return (
    <>
       setQuery(e.target.value)} />

  )
}

// After: only SearchBox re-renders while typing
function ShopPage() {
  return (
    <>

  )
}
```

### Technique 5: Pass JSX as children

A stateful wrapper, such as a collapsible panel or a scroll-aware header, re-renders whatever it renders directly. If it receives content as `children` instead, that content was created by the parent and React skips it when only the wrapper's state changes.

```
function Collapsible({ title, children }) {
  const [open, setOpen] = useState(false)
  return (

       setOpen(!open)}>{title}
      {open && children}

  )
}
```

> Before adding any memoization, ask where the state lives. Moving state down or content up fixes most re-render problems with zero runtime cost.

## Techniques 6 and 7: Keep interactions responsive with transitions and Activity

### Technique 6: useTransition and useDeferredValue

Some updates are urgent, like showing the character you just typed. Others can wait a few frames, like filtering 5,000 products. Marking the second kind as a transition lets React keep the input responsive and interrupt stale work, which directly improves INP.

```
import { useState, useTransition } from 'react'

function ProductFilter({ products }) {
  const [query, setQuery] = useState('')
  const [results, setResults] = useState(products)
  const [isPending, startTransition] = useTransition()

  function handleChange(e) {
    const value = e.target.value
    setQuery(value) // urgent: the input updates immediately
    startTransition(() => {
      setResults(filterProducts(products, value)) // can be interrupted
    })
  }

  return (
    <>

  )
}
```

When you do not control the state update, for example a value from props, `useDeferredValue(query)` gives you a lagging copy to render the expensive part with. In React 19.3, a slow transition no longer holds up unrelated transitions, so this pattern scales better on complex screens.

### Technique 7: Use Activity instead of unmounting hidden UI

Conditional rendering destroys a component's state and DOM when it hides, then rebuilds it from scratch when it returns. `<Activity>` hides the UI instead, cleans up its Effects, keeps its state, and defers its updates until React has nothing more urgent to do.

```

```

Use it for tabs, side drawers and screens users switch between often. Do not use it to keep dozens of heavy screens alive, because hidden trees still use memory.

## Technique 8: How to split code with lazy and Suspense

Every kilobyte of JavaScript must be downloaded, parsed and executed before the code it contains can run. Components that most users never open, such as a size guide modal, a rich text editor or a charting dashboard, should not be in the initial bundle.

```
import { lazy, Suspense, useState } from 'react'

const SizeGuide = lazy(() => import('./SizeGuide'))

function ProductActions() {
  const [open, setOpen] = useState(false)
  return (
    <>
       setOpen(true)}>Size guide
      {open && (
        }>
           setOpen(false)} />

      )}

  )
}
```

Give fallbacks the same dimensions as the final content to avoid layout shift. Then check the result with a bundle analyzer; how much you save depends entirely on what you split, so measure rather than assume. My [Core Web Vitals guide](https://amvprosox.com/blog/core-web-vitals-perfect-score-2025) covers the loading side in more detail.

## Technique 9: Virtualize long lists

Rendering 2,000 table rows creates tens of thousands of DOM nodes, and no amount of memoization fixes that. Virtualization renders only the rows inside the viewport plus a small buffer, and reuses them as the user scrolls.

```
import { useRef } from 'react'
import { useVirtualizer } from '@tanstack/react-virtual'

function OrderTable({ rows }) {
  const parentRef = useRef(null)
  const virtualizer = useVirtualizer({
    count: rows.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 48,
    overscan: 8,
  })

  return (

        {virtualizer.getVirtualItems().map((item) => (

        ))}

  )
}
```

The scroll container needs a fixed height and `overflow: auto` in your CSS. TanStack Virtual is headless and works with any markup. React Window and React Virtuoso are solid alternatives. Remember that virtualized rows are not in the DOM, so in-page browser search will not find them; add your own search for data-heavy tables.

## Technique 10: Move rendering work to the server

The fastest client code is code the client never downloads. React Server Components render on the server and send HTML plus a compact payload, with zero component JavaScript for the browser. Markdown parsing, syntax highlighting, date formatting libraries and data-heavy product descriptions are ideal candidates.

![Server racks packed with cables and lights, where static UI can be rendered](https://amvprosox.com/api/img/1793146657454-fk0ky2)
*Render static, data-heavy UI on the server and ship less JavaScript. Photo: Jefferson Lab, Public Domain*

- Keep interactive leaves, such as buttons, forms and carousels, as small Client Components.

- Push the `'use client'` directive as far down the tree as possible.

- Stream slow sections behind `Suspense` so they never block the first paint.

If you are new to the model, start with my [React 19 Server Components guide](https://amvprosox.com/blog/react-19-server-components-complete-guide), then see [what is new in Next.js and React in 2026](https://amvprosox.com/blog/nextjs-react-whats-new-2026) for how the frameworks build on it.

## Frequently asked questions

### Do I still need useMemo and useCallback with the React Compiler?

Rarely. With the compiler enabled, React handles routine memoization automatically and often more precisely than hand-written hooks. Keep `useMemo` and `useCallback` for cases that need exact control, such as stabilizing an Effect dependency. In existing code, leave current memoization in place and remove it gradually with tests.

### What is the most common cause of slow React apps?

In my experience it is state stored too high in the tree, combined with expensive work running on every keystroke or click. That causes large re-render trees and long main-thread tasks, which shows up as poor INP. Profiling usually points to one or two components, and moving state down plus a transition often fixes it.

### Is React fast enough for large ecommerce sites?

Yes, when it is built well. Server rendering, Server Components, code splitting and careful client boundaries let React storefronts meet Core Web Vitals targets. Problems come from shipping too much JavaScript, oversized images and third-party scripts, not from React itself. Measure field data and fix the biggest bottleneck first.

### Should I enable the React Compiler on an existing project?

Usually yes, but incrementally. Install the latest `eslint-plugin-react-hooks` first and fix rule violations, since the compiler skips components that break the Rules of React. Then enable it, run your test suite, and profile key interactions before and after. Expect slightly longer build times because it runs through Babel.

## Put these React performance techniques to work

Good React performance in 2026 comes from a short, disciplined loop: measure in production, let the compiler handle routine memoization, fix where state lives, keep interactions responsive with transitions, and ship less JavaScript through code splitting, virtualization and Server Components. Apply the techniques in that order and you will fix real bottlenecks instead of adding complexity.

If your React or Next.js app is failing Core Web Vitals and you want a second pair of eyes, [send me the details through my contact form](https://amvprosox.com/#contact-form). You can also explore my [case studies](https://amvprosox.com/case-studies) to see the sites and apps I have built.
