Skip to content
Now accepting new projects — limited slots available. Get started →
Performance · Updated Aug 3, 2026

What is Lazy Loading?

Lazy loading is a performance technique that defers loading off-screen resources until they're needed.

What is Lazy Loading?

Lazy loading defers fetching images, iframes, JavaScript modules, or components until the user scrolls near them. The HTML loading="lazy" attribute—standardized in Chrome 76 (2019), now in all major browsers including Safari 15.4+ (2022)—handles this natively without JavaScript. It cuts initial page weight, drops Time to Interactive, and improves Largest Contentful Paint scores. Unless you screw it up.

The most common mistake I fix: developers slap loading="lazy" on the hero image. That tanks LCP. Don't lazy load above-the-fold content. Ever.

A typical e-commerce listing with 40+ thumbnails? Lazy loading cuts initial transfer by 60–80%. We've shipped this on 50+ projects.

How it works

Two main approaches:

Native browser lazy loading

Add loading="lazy" to <img> or <iframe>:

<img src="/products/shoe-42.webp"
     alt="Running shoe model 42"
     width="400"
     height="300"
     loading="lazy"
     decoding="async" />

The browser picks a threshold—usually ~1250px below the viewport on fast connections, ~2500px on slow ones in Chromium—and starts fetching before the element appears. You don't control this. The browser does it based on connection speed and device.

Intersection Observer API

For finer control—React components, video elements, animations—use IntersectionObserver:

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

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

Framework-level lazy loading

Next.js <Image> applies loading="lazy" by default. Use priority={true} to opt out for above-the-fold images. Astro's <Image /> does the same. In React, React.lazy() with Suspense handles component-level code splitting—conceptual lazy loading for JavaScript bundles.

Critical detail: always set explicit width and height (or CSS aspect-ratio) on lazy-loaded images. Otherwise you'll blow your Cumulative Layout Shift score.

When to use it

Lazy loading is default-on for most below-the-fold content. But it's not universal.

Use lazy loading when:

  • Long pages with many images (blogs, product grids, galleries)
  • Embedding third-party iframes (YouTube, maps) below the fold
  • Loading heavy components conditionally (dashboards, modals, tab content)
  • Initial payload exceeds ~1.5 MB uncompressed

Don't use lazy loading when:

  • The resource is above the fold—your LCP element (hero image, banner) needs loading="eager" or priority={true} in Next.js, ideally with fetchpriority="high"
  • The page has fewer than ~3 images total—overhead isn't worth it
  • You're dealing with print stylesheets where off-screen content must render
  • Critical fonts or CSS—these load eagerly, not lazily

My preferred pattern: lazy load everything by default, then explicitly mark above-the-fold assets as eager. It's harder to forget to lazy load than to forget to prioritize.

Lazy Loading vs alternatives

Technique What it defers Trigger JS Required Browser Support
loading="lazy" Images, iframes Viewport proximity No All modern (2022+)
Intersection Observer Any DOM element Custom threshold Yes All modern
fetchpriority hint Nothing—it reprioritizes Immediate No Chromium 101+, Firefox 132+, Safari 17.2+
Content-Visibility: auto Rendering cost Viewport proximity No Chromium 85+, Firefox 125+
React.lazy() / dynamic imports JS bundles Route or interaction Yes (bundler) All modern

loading="lazy" and fetchpriority aren't competing. They're complementary. I use both on the same page: fetchpriority="high" on the hero, loading="lazy" on everything else. content-visibility: auto is the more aggressive cousin—it skips rendering entirely, not just fetching—and pairs well with lazy loading on very long pages.

Real-world example

We rebuilt a recipe blog in Astro 4 (800+ posts, averaging 12 images per post). Before optimization: 4.2 MB initial transfer, 4.8s LCP on mobile (3G throttled). We applied native loading="lazy" to all images except the hero, converted to WebP/AVIF with Astro's <Image />, set explicit dimensions on every element, and added fetchpriority="high" to the hero.

Result: initial transfer dropped to 680 KB, LCP fell to 1.9s, CLS stayed at 0 because every image had reserved space. Lighthouse performance score jumped from 52 to 94. The entire change was about 30 lines of template code. No custom JavaScript.

Frequently asked questions about Lazy Loading

Is lazy loading the same as code splitting?
They're related but different. Lazy loading is the broader concept of deferring any resource until it's needed. Code splitting specifically refers to breaking JavaScript bundles into smaller chunks—typically at the route or component level—using dynamic `import()` syntax. Code splitting is *one form* of lazy loading applied to JS modules. When people say 'lazy loading' in a web performance context, they usually mean deferring images and iframes via `loading="lazy"` or Intersection Observer. Code splitting is what tools like webpack, Vite, and Turbopack do to your JavaScript. You often use both together: code-split your routes, and lazy load images within each route.
When did lazy loading become a browser standard?
Chrome 76 shipped native `loading="lazy"` in July 2019, making it the first browser with built-in support. Firefox followed in Firefox 75 (April 2020). The long holdout was Safari—Apple added support in Safari 15.4, released in March 2022. Since then, all major browsers support it natively. Before native support, developers relied on libraries like `lazysizes` (first released around 2015) or custom Intersection Observer implementations. The `loading` attribute is now part of the HTML Living Standard maintained by WHATWG. As of April 2026, global browser support for native lazy loading sits above 96% according to caniuse data.
What's the alternative to lazy loading images?
If you can't or shouldn't lazy load, the main alternatives are: (1) aggressive image compression and modern formats (AVIF, WebP) to reduce payload even when loading eagerly, (2) responsive images via `srcset` and `sizes` to serve appropriately sized files per device, and (3) `content-visibility: auto` in CSS, which doesn't defer the *fetch* but skips the rendering cost of off-screen content. For JavaScript, if you don't want dynamic imports, tree-shaking and dead code elimination via your bundler reduce initial bundle size without deferred loading. In practice, most sites should just use lazy loading—the alternatives are complements, not replacements.
Does lazy loading hurt SEO?
No—when done correctly. Googlebot renders pages with a Chromium-based renderer that supports `loading="lazy"` and Intersection Observer, so lazy-loaded images are discoverable for indexing. Google has confirmed this in their developer documentation. The risk is if you implement lazy loading incorrectly—for example, using JavaScript-only approaches that prevent the `src` attribute from ever appearing in the raw HTML, which could affect non-JS crawlers. Native `loading="lazy"` is safest because the actual `src` is always in the markup. One real concern: if your LCP image is incorrectly lazy loaded, your Core Web Vitals scores drop, and since LCP is a ranking signal, that *indirectly* hurts SEO. Always mark above-the-fold images as eager.
Get in touch

Let's build
something together.

Whether it's a migration, a new build, or an SEO challenge — the Social Animal team would love to hear from you.

Get in touch →