MiniPx
Blog
๐Ÿ”’ Images never leave your browser
โ† Blog

How to Lazy Load Images the Right Way

By Gaurav Bhowmickยทยท7 min read

Images are usually the heaviest assets on any web page. A typical e-commerce page loads 8-15 MB of images, but the user only sees the first 2-3 when the page opens. Lazy loading defers off-screen images until the user scrolls near them. It is the single biggest performance win you can get with one line of code.

The simplest approach: native lazy loading

Modern browsers support lazy loading natively with a single HTML attribute. No JavaScript library required.

<img src="product-photo.jpg"
     loading="lazy"
     width="800"
     height="600"
     alt="Blue running shoes side view" />

That is it. The browser decides when to start fetching based on scroll position. Chrome starts loading about 1250 pixels before an image enters the viewport on fast connections, and 2500 pixels on slow connections.

Always include width and height. Without dimensions, the browser cannot reserve space in the layout, which causes Cumulative Layout Shift (CLS) โ€” one of the three Core Web Vitals.

What NOT to lazy load

Your hero image, logo, and any image visible in the initial viewport should load eagerly. Lazy loading these hurts your Largest Contentful Paint (LCP) because you are telling the browser to delay the most important visual on the page.

<!-- Hero: load eagerly with high priority -->
<img src="hero.jpg"
     loading="eager"
     fetchpriority="high"
     width="1200" height="600" alt="..." />

<!-- Below the fold: lazy load -->
<img src="feature-1.jpg" loading="lazy"
     width="600" height="400" alt="..." />
<img src="feature-2.jpg" loading="lazy"
     width="600" height="400" alt="..." />

The fetchpriority="high" attribute tells the browser to prioritize your hero image in the loading queue. Pair it with a <link rel="preload"> in your document head for even faster delivery.

Intersection Observer for custom behavior

Need custom loading thresholds, fade-in animations, or loading indicators? Use the Intersection Observer API.

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      img.classList.add('loaded');
      observer.unobserve(img);
    }
  });
}, { rootMargin: '200px' });

document.querySelectorAll('img[data-src]')
  .forEach(img => observer.observe(img));

The rootMargin option controls how far ahead of the viewport the observer fires. Setting it to 200px means images start loading when they are 200 pixels below the visible area, giving the browser time to fetch before the user scrolls to them.

Lazy loading in React and Next.js

The Next.js Image component handles lazy loading automatically. Below-the-fold images are lazy loaded by default. Above-the-fold images get the priority prop.

import Image from 'next/image';

// Above the fold โ€” priority loading
<Image src="/hero.jpg" priority
       width={1200} height={600} alt="..." />

// Below the fold โ€” lazy loaded by default
<Image src="/feature.jpg"
       width={600} height={400} alt="..." />

The Next.js Image component also generates responsive srcset attributes and serves modern formats (WebP/AVIF) when supported. You get lazy loading, responsive images, and format optimization from a single component.

Lazy loading + compression = fastest pages

Lazy loading controls when images load. Compression controls how large each image is. Together, they are the two most effective image performance techniques.

Compress your images first using a tool like MiniPx, then serve them with lazy loading. A compressed, lazy-loaded image gallery loads 10x faster than an uncompressed, eagerly-loaded one. On a page with 20 images, that is the difference between a 1.5 MB initial load and a 15 MB one.

Your Core Web Vitals will thank you โ€” faster LCP, lower total page weight, and less wasted bandwidth for users who never scroll to the bottom.

Frequently asked questions

Does lazy loading hurt SEO?
No. Google fully supports native lazy loading with loading="lazy". Googlebot renders JavaScript and discovers lazy-loaded images. Just make sure above-the-fold images are NOT lazy loaded โ€” those should load immediately.
Should I lazy load all images?
No. Images visible when the page first loads (above the fold) should load immediately. Only lazy load images below the fold. Lazy loading above-the-fold images hurts LCP because the browser delays content the user sees right away.
What is the difference between loading="lazy" and Intersection Observer?
loading="lazy" is a native HTML attribute โ€” the browser handles everything automatically. Intersection Observer is a JavaScript API that gives you manual control over thresholds and custom behavior. The native attribute is simpler; the Observer gives more flexibility.
Does lazy loading work on all mobile browsers?
The loading="lazy" attribute is supported in Chrome, Firefox, Edge, Safari 15.4+, and Samsung Internet. On older browsers, the attribute is ignored and images load normally โ€” it degrades gracefully.

Related tools

Compress JPEGCompress PNGConvert JPG to WebP

More from the blog

JPEG vs PNG vs WebP โ€” Best Format for Websites โ†’Optimize Images for Core Web Vitals โ€” Complete Guide โ†’
๐Ÿ”ง
Try MiniPx โ€” free, no signup

Compress, convert, and resize images in your browser. Nothing gets uploaded.

Open MiniPx โ†’