If you search for React performance tips, you'll probably find the same advice everywhere:
- Use
useMemo - Use
useCallback - Wrap everything in
React.memo
Those tools definitely have their place, but after working on production applications, I realized they weren't the optimizations that made the biggest difference.
Most of the improvements came from asking much simpler questions.
- Do we really need to load everything right now?
- Are we making API calls users may never need?
- Can we reduce the amount of JavaScript we're sending to the browser?
- Can we make the application feel faster, even if the network speed stays the same?
Here are some of the optimizations that had the biggest impact for me.
Loading only what users actually need
One of the easiest wins was route-based code splitting.
Early on, every page was bundled together, so users downloaded JavaScript for screens they might never visit.
Switching to React.lazy() meant each page was loaded only when someone navigated to it.
const Dashboard = React.lazy(() => import("./pages/Dashboard"));
It wasn't a huge code change, but the application felt noticeably lighter on the first load.
Stopping unnecessary API calls
This was probably the optimization that changed the way I think about frontend development.
Initially, we fetched almost everything as soon as a page loaded.
Later, we started asking whether that data was actually needed.
For example:
- fetch modal data only after the modal opens
- load report details after a report is selected
- request dropdown options only when the dropdown is opened
The UI became faster because we weren't waiting on requests the user might never trigger.
Sometimes performance is just... fewer requests
I used to think performance was mostly about rendering.
Now I think it's just as much about reducing unnecessary work.
If the browser doesn't have to make an API call, parse a response, and render new data, that's already a performance improvement.
Not every optimization needs to involve React hooks.
Images don't need to load all at once
This sounds obvious, but it's surprisingly easy to overlook.
If a page contains dozens of images, loading all of them immediately doesn't help anyone.
Using native lazy loading solved that with almost no effort.
<img loading="lazy" src={image} alt="Poster" />
It's one of those changes that's almost invisible in the codebase but easy to notice when using the application.
I became much more careful with memoization
A few years ago I probably would've wrapped everything in useMemo because that's what every React performance article recommended.
Now I'm much more selective.
If a component is inexpensive to render, I usually leave it alone.
If it's rendering a large dataset or doing expensive calculations, then memoization starts making sense.
React DevTools Profiler helped me make those decisions instead of guessing.
Skeleton loaders made a bigger difference than I expected
One thing I learned is that users don't always care whether something is technically faster.
They care whether it feels faster.
Replacing spinners with skeleton loaders made the application feel much smoother, even though the actual loading time stayed almost the same.
Sometimes perception matters just as much as raw performance.
Accessibility helped performance too
One project involved improving accessibility using semantic HTML and proper ARIA attributes.
The primary goal wasn't performance, but while cleaning up the markup and improving the overall structure, our Lighthouse score improved from 65 to 91.
It was a good reminder that accessibility, maintainability, and performance often go hand in hand.
The biggest lesson I learned
Looking back, the biggest improvements didn't come from clever React tricks.
They came from reducing unnecessary work.
- Less JavaScript.
- Fewer API calls.
- Smaller bundles.
- Loading data only when users actually needed it.
Making thoughtful architectural decisions had a much bigger impact than trying to optimize every render.
Final thoughts
Performance is one of those things that's easy to overcomplicate.
I've learned that it's usually better to start by measuring the problem, understand where the bottlenecks actually are, and then fix the things that matter.
Sometimes the best optimization isn't adding another hook.
Sometimes it's simply not doing the work in the first place.
What are some performance optimizations you've found useful in your React projects? I'd love to hear them in the comments.
Top comments (0)