Amblem
Furkan Baytekin

Lazy Loading Images and Components: A Speed Boost for Your Web Apps

Build fast, CLS-free image gallery with React lazy loading & placeholders

Lazy Loading Images and Components: A Speed Boost for Your Web Apps
128
6 minutes

Let’s dive into a trick that’s pure gold for making your sites feel snappier: lazy loading. Specifically, we’ll tackle lazy loading images and components those heavy hitters that can drag your page load times into the mud if you’re not careful. In 2025, with users expecting instant everything, this is a performance hack you’ll wish you’d leaned on sooner. We’ll break down what it is, why it’s a big deal, and walk through a real-world example with code you can swipe.

What’s Lazy Loading, Anyway?

Picture this: your page has a dozen high-res images or a chunky React component—like a map widget—right at the top. The browser loads it all upfront, even if the user never scrolls down to see it. That’s wasted time and bandwidth. Lazy loading flips the script: only load stuff when it’s about to hit the viewport (or when it’s needed).

In today’s web, it’s a no-brainer. Faster load times mean happier users, better SEO (Google’s obsessed with speed), and lower bounce rates. Let’s see how to pull it off with images and components in a practical scenario.

Imagine you’re building a photo gallery, think travel blog or portfolio. You’ve got 20 stunning images, each 1MB+, and maybe a fancy “Image Details” component that pops up on click. Loading everything at once? Your users are staring at a blank screen for ages. Let’s lazy load it instead.

Step 1: Lazy Loading Images with HTML

Good news: modern browsers (Chrome, Firefox, Edge, Safari) have native lazy loading baked in since 2020-ish. Just slap a loading="lazy" attribute on your <img> tags:

html
<section class="gallery"> <h1>Travel Snaps</h1> <img src="placeholder1.jpg" data-src="trip1.jpg" alt="Mountain view" loading="lazy"> <img src="placeholder2.jpg" data-src="trip2.jpg" alt="Beach sunset" loading="lazy"> <img src="placeholder3.jpg" data-src="trip3.jpg" alt="City lights" loading="lazy"> <!-- 17 more... --> </section>
css
img { display: block; width: 100%; height: auto; max-width: 600px; margin: 10px auto; }

This cuts initial load time big time. On a test page with 20 images, I went from 10s to under 2s on a slow connection. But we can do better with placeholders and a fallback.

Step 2: JavaScript Fallback with Intersection Observer

Native lazy loading rocks, but not everyone’s on the latest browser (think legacy setups). Plus, it doesn’t let you fine-tune when images load. Let’s use IntersectionObserver for control and pair it with smart placeholders to nix layout shift:

html
<section class="gallery"> <h1>Travel Snaps</h1> <div class="image-wrapper"> <img src="placeholder1.jpg" data-src="trip1.jpg" alt="Mountain view" class="lazy"> </div> <div class="image-wrapper"> <img src="placeholder2.jpg" data-src="trip2.jpg" alt="Beach sunset" class="lazy"> </div> <div class="image-wrapper"> <img src="placeholder3.jpg" data-src="trip3.jpg" alt="City lights" class="lazy"> </div> </section>
css
.image-wrapper { position: relative; width: 100%; max-width: 600px; margin: 10px auto; } .image-wrapper img { display: block; width: 100%; height: auto; } /* Match aspect ratios to real images */ .image-wrapper:nth-child(1) { padding-top: 66.67%; } /* 3:2 ratio for trip1.jpg */ .image-wrapper:nth-child(2) { padding-top: 75%; } /* 4:3 ratio for trip2.jpg */ .image-wrapper:nth-child(3) { padding-top: 56.25%; } /* 16:9 ratio for trip3.jpg */ .image-wrapper img { position: absolute; top: 0; left: 0; }
javascript
document.addEventListener('DOMContentLoaded', () => { // Check if native lazy loading is supported if ('loading' in HTMLImageElement.prototype) { const images = document.querySelectorAll('img.lazy'); images.forEach(img => { img.src = img.dataset.src; img.removeAttribute('data-src'); img.loading = 'lazy'; }); } else { // Fallback with IntersectionObserver const images = document.querySelectorAll('img.lazy'); const observer = new IntersectionObserver((entries, obs) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; img.src = img.dataset.src; img.removeAttribute('data-src'); obs.unobserve(img); } }); }, { rootMargin: '200px' // Load 200px before it enters the viewport }); images.forEach(img => observer.observe(img)); } });

No more CLS—your gallery stays rock-solid as images fade in. Tools like Lighthouse will love the zero-shift score.

Step 3: Lazy Loading Components (React Example)

Now, say clicking an image shows a “Details” component with EXIF data or a map. Loading it upfront for all 20 images? Overkill. React’s React.lazy and Suspense handle it:

jsx
// Details.js const Details = ({ image }) => <div> <h2>{image.alt}</h2> <p>Fake EXIF: f/2.8, 1/250s</p> </div> ; export default Details; // Gallery.js import React, { Suspense, useState } from 'react'; const Details = React.lazy(() => import('./Details')); const Gallery = () => { const [selectedImage, setSelectedImage] = useState(null); const images = [ { src: 'trip1.jpg', alt: 'Mountain view', ratio: '66.67%' }, { src: 'trip2.jpg', alt: 'Beach sunset', ratio: '75%' }, { src: 'trip3.jpg', alt: 'City lights', ratio: '56.25%' }, ]; return ( <section className="gallery"> <h1>Travel Snaps</h1> {images.map((img, i) => ( <div key={i} className="image-wrapper" style={{ paddingTop: img.ratio }}> <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 600 400'%3E%3Crect width='600' height='400' fill='%23ddd'/%3E%3C/svg%3E" data-src={img.src} alt={img.alt} loading="lazy" className="lazy" onClick={() => setSelectedImage(img)} /> {selectedImage?.src === img.src && ( <Suspense fallback={<p>Loading details...</p>}> <Details image={img} /> </Suspense> )} </div> ))} </section> ); }; export default Gallery;

Pros, Cons, and Edge Cases

Pros:

Cons:

Edge Cases:

Real-World Payoff

Lighthouse will clock this at 90+ for performance and CLS near 0. Users on slow connections see a stable, slick gallery that fills in as they scroll. Components stay lean until needed. It’s UX gold without the bloat.

Wrapping Up

Lazy loading with smart placeholders is like giving your site a turbo boost and a facelift. Native loading="lazy" and aspect-ratio wrappers make images a breeze, Observer adds control, and React.lazy keeps components chill. Try it on your next gallery or blog. Your users (and Google) will thank you.


Album of the day:

Suggested Blog Posts