Note: This video and podcast was generated using AI, adapting the original content and technical insights created by the author of the blog post.
React performance has changed over the years. We used to fight to keep the number of renders as low as possible. Then hooks like useMemo became the key to performance optimization. But the JavaScript and React ecosystems evolve quickly, and today, things we once had to worry about are available out of the box. Of course, that doesn’t mean we no longer have to care about performance! Let’s see what makes the biggest difference when it comes to performance optimization in 2026.
React Compiler changed the memoization game
If you were part of the React community before 2025, you probably know how much attention was given to using useMemo and useCallback. It was also practically a standard interview question.
Want to make your application faster? Do this:
const filteredUsers = useMemo(
() => users.filter(user => user.active),
[users]
);
const handleClick = useCallback(
(id: string) => {
selectUser(id);
},
[selectUser]
);
Some people even wrote entire essays online about whether these functions should be used everywhere by default or only when we actually had reasons to worry that a function would run too often. Even back then, I found it strange that we had to focus on something like this during development and wondered why the library couldn’t simply do it for us. Apparently, more people thought the same, because React Compiler solved this problem. The compiler can now memoize computations and components where it is safe to do so.
It’s worth adding that manual memoization still has its use cases sometimes, but it’s more of an exception than a rule. Our code can look like this again:
function UserList({ users, onSelect }) {
const activeUsers = users.filter(user => user.active);
const handleClick = (id: string) => {
onSelect(id);
};
return activeUsers.map(user => (
<User
key={user.id}
user={user}
onClick={handleClick}
/>
));
}
Does this mean the end of our application performance problems? Of course not. That would be too good to be true! We’ve only gotten rid of the need to create memoization-related micro-optimizations. The bad news is that React Compiler won’t fix an application that downloads 8MB of JavaScript on startup or generates enormous numbers of DOM elements.
So in 2026, the question shouldn’t be: “Did I add useMemo often enough?” but rather, “Am I even optimizing the right thing?”
Before you continue…
The reading list you'd build – if you had time.
The reads you'd find if you had time
Experts you can actually ask
Deep dives worth your weekend
Past conferences, ready when you are
Measure before you optimize
This advice is as old as web development itself, but I have the impression that some people are surprisingly resistant to it. I’ve often seen code like this:
const total = items.reduce((sum, item) => sum + item.price, 0);
In the past, I often heard that this was a perfect candidate for useMemo because code like this needed to be optimized. But when you look more closely, it turns out that items usually contains 30 elements, and reduce executes in a fraction of a second. Meanwhile, before this code even runs, we have to load 3MB of JavaScript.
That’s why, before optimizing, we first need to answer a few questions: what is actually slow? Startup? Interaction? Rendering? A request? Hydration? Scrolling? Once we’ve established what is slowing the application down, it’s worth figuring out why it’s slow. The answers can vary significantly here, and we have plenty of tools to help us, such as:
- React DevTools Profiler,
- Chrome Performance panel,
- Network panel,
- Lighthouse / Core Web Vitals,
- bundle analyzer,
performance.mark()/performance.measure()for our own critical paths.
So the simplest possible profiling could look like this:
performance.mark('search-start');
const results = await searchProducts(query);
performance.mark('search-end');
performance.measure(
'product-search',
'search-start',
'search-end'
);
Profiling a specific part of React, on the other hand, could look like this:
<Profiler
id="ProductList"
onRender={(id, phase, actualDuration) => {
console.log({
id,
phase,
actualDuration
});
}}
>
<ProductList products={products} />
</Profiler>
The point is simply not to optimize things that don’t need optimization, but rather to focus on the parts that can have a real impact on performance. Browser engines in 2026 are already really fast, and if something is genuinely slow, we usually have a real problem and micro-optimizations simply won’t help.
So the order should be exactly this: locate the problem and fix it. Not: put useMemo everywhere and hope something improves.
Your biggest performance problem may happen before React renders anything
This is an often-overlooked truth. Ok, your components are beautifully compiled and render in milliseconds. But before the application even starts, it has to download a huge bundle, parse the JavaScript, and compile it. And let’s not forget that enormous library it has to initialize. Then we just need to make a dozen requests, followed by a few more that depend on the previous ones.
A common mistake is an enormous bundle size created almost by accident because of suboptimal application architecture. For example, we might have code like this:
import HugeChartingLibrary from 'huge-charting-library';
Meanwhile, we use it to render a single chart that is inside a modal opened by 5% of the users. Something like this would make much more sense:
const AnalyticsChart = lazy(() => import('./AnalyticsChart'));
function Dashboard() {
const [showAnalytics, setShowAnalytics] = useState(false);
return (
<>
<button onClick={() => setShowAnalytics(true)}>
Show analytics
</button>
{showAnalytics && (
<Suspense fallback={<ChartSkeleton />}>
<AnalyticsChart />
</Suspense>
)}
</>
);
}
But be careful! Code splitting isn’t the answer to every problem either. Creating chunks too aggressively can increase network overhead, and poorly designed loading can even lead to additional waterfalls.
In general, we should be very careful about what ends up in our bundle. Think about how many lines of code you’ve written/generated yourself and how much you’re actually shipping to the user in the form of huge libraries when you only use a fraction of their functionality. And of course, remember the network and network waterfalls. This is fairly obvious to some people, but let’s do a quick refresher. If you have calls like these:
const user = await getUser();
const projects = await getProjects(user.id);
const permissions = await getPermissions(user.id);
const notifications = await getNotifications(user.id);
And the next three depend on the first one; it’s worth solving it like this:
const user = await getUser();
const [projects, permissions, notifications] =
await Promise.all([
getProjects(user.id),
getPermissions(user.id),
getNotifications(user.id)
]);
Keeping an eye on things like this can seriously optimize your application much more than optimizing a dozen renders.
Data architecture can matter more than React itself
Very often, in large applications, React isn’t actually the biggest problem. It’s the way data flows through the application. Let’s imagine some simple code like this:
function App() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('/api/products')
.then(res => res.json())
.then(setData);
}, []);
// ...
}
And that’s exactly the point! The code itself is correct. In small applications, it’s probably perfectly sufficient. But in huge enterprise apps with tons of requests, things aren’t that simple. Suddenly, we care about questions like: When does this actually load? Do multiple pages reuse this list? Is there a cache? When we return to the page, do we fetch everything all over again, etc.? Fortunately, we have many ways to manage this; for example, TanStack Query:
const { data, isPending } = useQuery({
queryKey: ['products', category],
queryFn: () => fetchProducts(category),
staleTime: 60_000
});
Here, we get much more control over what happens in our application. Very often, better-controlled requests are the real performance lifesaver and give us much more than dozens of micro-optimizations.
Server Components: where they make sense
Server Components are quite popular, and of course, there’s a good reason for that. They allow us to move logic and data fetching to the server without increasing the client-side JavaScript bundle.
async function ProductPage({ id }) {
const product = await db.product.findUnique({
where: { id }
});
return <ProductDetails product={product} />;
}
Yes, this doesn’t have to be part of the initial bundle and can be executed on the server. We can save the browser quite a lot of work. Server components aren’t the answer to everything. They’re especially useful for e-commerce pages, huge dashboards, and content-heavy websites. But remember that this doesn’t mean we should move everything to the server. Doing so won’t solve all our problems. Server components won’t help us much in places that inherently require client-side state, event handlers, or direct access to browser APIs. Imagine some code like this:
'use client';
function DrawingCanvas() {
// pointer events
// local state
// animations
// immediate feedback
}
There’s not much point in moving this to the server. Let’s also not forget that it’s perfectly possible to create terrible data architecture and server-side waterfalls on the server too. Moving things there doesn’t give us permission to stop caring about code quality.
When rendering performance actually matters
It’s often true that the number of renders isn’t always the most important thing. But there are places where it still absolutely matters. Take this code:
{products.map(product => (
<ProductRow
key={product.id}
product={product}
/>
))}
If you have 10, or even 50, items on the list, everything is fine. But what if there are 50,000? Sometimes that’s actually necessary! That’s where virtualization comes in, and here it’s the absolute minimum optimization:
const virtualizer = useVirtualizer({
count: products.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 40
});
The browser doesn’t need to render 50,000 elements when the user can only see 20. Another example is expensive visualizations or data that updates frequently. For example, if a render happens once when a button is clicked, nothing bad is happening. But imagine something like this:
window.addEventListener('pointermove', event => {
setPosition({
x: event.clientX,
y: event.clientY
});
});
Now we start thinking about things like batching, throttling, requestAnimationFrame, or refs instead of state, where a re-render isn’t needed. Sometimes it’s even worth moving part of the rendering to canvas or WebGPU instead of the DOM. React is great at rendering, but the DOM simply isn’t always the best rendering engine for every workload.
Before you continue…
The reading list you'd build – if you had time.
The reads you'd find if you had time
Experts you can actually ask
Deep dives worth your weekend
Past conferences, ready when you are
The React Performance Checklist for 2026
Here’s a short checklist of what we should do to improve performance:
- Measure first. Don’t take shortcuts and don’t guess. Find the actual bottleneck.
- Check what happens before React. Check the bundle size and large libraries, and see whether you have network waterfalls.
- Fix data fetching architecture. Especially in large applications, ask yourself whether you’re optimizing data fetching correctly.
- Ship less JavaScript. Remember things like code splitting, lazy loading, and server/client boundaries if you’re using SSR.
- Let React Compiler handle routine memoization. Don’t start optimization by manually sprinkling
useMemoall over your application. That’s no longer necessary. - Reduce work instead of making unnecessary work slightly faster. Your optimization shouldn’t be about making 50K rows in a table render faster. Don’t render that many in the first place.
- Optimize rendering when rendering is actually the bottleneck. Remember virtualization and expensive visualizations. Watch out for high-frequency updates.
- Measure again. Repeat step one. “It feels faster” is unfortunately not a good metric.
As we can see, in 2026 React itself increasingly gets out of the way and helps us make applications faster and efficiently. The problems are more often found in our architecture and in how we manage the application. Meanwhile, the best optimization often isn’t about making expensive operations run faster but about avoiding them altogether.





