The easiest performance win in React isn't always your JavaScript. Sometimes it's your images.
I used to think performance optimization meant splitting bundles, memoizing components, and chasing Lighthouse scores.
Then I opened Chrome DevTools on one of my own React projects.
The result? Images were quietly eating most of the page weight — way more than my JS bundle, my fonts, everything else combined.
A hero banner that looked perfect on my laptop was making mobile users download a multi-megabyte file just to see a heading.
That's when it clicked: image optimization isn't a nice-to-have. It's one of the fastest ways to make a React site actually feel faster.
Here's exactly what I changed.
Why Image Optimization Matters
Every extra second of load time costs you something — a visitor who bounces, a search ranking that slips, a bandwidth bill that keeps climbing.
None of that is dramatic on its own. But it adds up fast, and images are usually where it's hiding.
First Rule: Stop Uploading Giant Images
We've all done it.
Designer sends a beautiful 4000×3000 image.
We drop it into React.
Then display it inside a 400px card.
The browser still downloads the full file — every pixel of it, whether you show it or not.
Instead, match your image size to where it's actually displayed:
| Device | Image Width |
|---|---|
| Mobile | 400px |
| Tablet | 800px |
| Desktop | 1200px |
Your users won't notice the difference in quality. They'll definitely notice the faster loading.
The Two Formats I Use for Almost Every Project
WebP — if you're still serving JPEGs everywhere, you're leaving free performance on the table. WebP usually keeps the same visual quality while shrinking the file size significantly.
Example:
JPEG → 500KB
WebP → around 320KB
That's hundreds of kilobytes saved without changing your design at all.
AVIF — takes compression even further. I've seen hero images drop from 500KB to nearly 200KB with almost no visible quality loss. Browser support is good enough now that it's worth using wherever you can.
Lazy Loading Is Basically Free Performance
One thing I always ask myself now: "Does this image need to load immediately?"
Usually the answer is no.
Gallery images. Blog thumbnails. Testimonials. Footer logos. All of these can wait until users actually scroll near them.
React makes it ridiculously simple:
<img
src="/gallery/photo.webp"
alt="Gallery"
loading="lazy"
/>
One attribute. Less network traffic. Faster initial load.
But Don't Lazy Load Everything
Your hero image is different. If it's the biggest thing users see first, load it immediately — not lazily.
The Hero Image Can Make or Break Your LCP
Google's Largest Contentful Paint (LCP) is often just your hero image, plain and simple.
If it's delayed, your performance score suffers. Instead of this:
<img src="/hero.webp" loading="lazy" />
Use this:
<img
src="/hero.webp"
loading="eager"
fetchPriority="high"
alt="Hero"
/>
Small change. But now the browser knows this image deserves priority over everything else on the page.
The Page Jump Problem (CLS)
Have you ever tried clicking something and the page suddenly moved because an image loaded underneath your cursor?
That's Cumulative Layout Shift. The browser didn't know how much space to reserve, so it grabbed space the moment the image arrived — shoving everything else out of the way.
Bad:
<img src="/product.jpg" alt="Product" />
Better:
<img
src="/product.jpg"
alt="Product"
width={600}
height={400}
/>
Or use CSS:
.product-image {
aspect-ratio: 3 / 2;
}
Now the layout stays stable while the image loads, no matter how slow the connection is.
Mobile Users Don't Need Desktop Images
This is probably the biggest missed opportunity I see in React codebases.
Why send a 1200px image to someone on a 390px phone? That's exactly why srcSet exists:
<img
src="/image-800.webp"
srcSet="
/image-400.webp 400w,
/image-800.webp 800w,
/image-1200.webp 1200w
"
sizes="(max-width: 768px) 100vw, 800px"
alt="Responsive"
/>
The browser automatically picks the right version. No complicated logic. No JavaScript required.
The Image Component I Kept Rebuilding
After repeating these optimizations across multiple projects, I noticed I was writing the same image component again and again. Every project needed:
Lazy loading
Blur placeholders
Skeleton loaders
WebP support
AVIF support
Priority loading for hero images
Aspect ratio to prevent CLS
So I turned it into a reusable package.
React Smart Image
Instead of wiring everything manually, I can now just write:
import { SmartImage } from "@concatstring/react-smart-image";
<SmartImage
src="/hero.webp"
alt="Hero"
width={1200}
height={700}
priority
aspectRatio="12:7"
/>
It handles most of these best practices automatically — which saves real time, especially on bigger React projects with hundreds of images scattered across pages.
My Go-To Image Optimization Checklist
Before shipping any React page, I run through this quickly:
Resize images before uploading
Use WebP or AVIF whenever possible
Lazy load below-the-fold images
Prioritize the hero image
Set width and height (or aspect ratio)
Use srcSet for responsive images
Five minutes on this list usually saves users several seconds of loading time. Honestly, it's one of the highest-ROI optimizations I've found in React — full stop.
Try React Smart Image
If you don't want to configure every image manually, check out the package I built.
React Smart Image — lazy loading, blur placeholders, skeleton loaders, WebP & AVIF support, LCP priority loading, responsive images, and CLS prevention, all in one component.
NPM Package: https://www.npmjs.com/package/@concatstring/react-smart-image
GitHub: https://github.com/concatstring-account/react-smart-image
Live Demo: https://react-smart-image.netlify.app/
Product Page: https://labs.concatstring.com/products/react-smart-image
If you're building React apps regularly, these small image optimizations add up — and your users (and your Lighthouse score) will thank you for it.






Top comments (0)