What Is Lazy Loading and How to Implement It on Images Without Hurting SEO

On image-heavy sites (portfolios, agency showcases, photography, e-commerce lookbooks), lazy loading looks like free performance. Add loading=”lazy” everywhere, ship it, move on. Then PageSpeed Insights comes back with a red Largest Contentful Paint and a warning you did not expect: “Largest Contentful Paint image was lazily loaded”.

That is the paradox of lazy loading images for SEO. Done selectively, it is one of the cheapest performance wins available. Done to every image on the page, it actively delays the single visual that Google measures for speed. This guide explains the difference, with the exact markup, the exclusion rules, and the verification steps we use at Pixelbright on design-driven builds.

What is lazy loading, in plain terms?

Lazy loading is a strategy that tells the browser: this resource is not critical right now, fetch it only when the user is about to need it. For images, that means an image sitting 3,000 pixels down the page is not downloaded during the initial page load. It is downloaded when the user scrolls close to it.

The benefits on an image-heavy layout are real:

  • Fewer bytes on initial load, which matters enormously on mobile data
  • Less bandwidth contention, so the images that are visible get downloaded faster
  • Lower hosting and CDN costs on galleries where most visitors never scroll to the bottom
  • Better Core Web Vitals, as long as you exclude the right images

Historically this required JavaScript libraries and an IntersectionObserver. Today, browsers do it natively with a single HTML attribute, and that is what you should be using in 2026 unless you have a very specific reason not to.

photo gallery website

Native lazy loading: the loading attribute

The implementation is one attribute on the <img> tag:

<img src="/images/project-detail.webp"
     alt="Interior shot of the Maison Verre lobby"
     width="1200"
     height="800"
     loading="lazy"
     decoding="async">

It works the same way on iframes:

<iframe src="https://player.vimeo.com/video/123456" loading="lazy" title="Showreel"></iframe>

Native lazy loading is supported in every current major browser (Chrome, Edge, Firefox, Safari). Browsers that do not understand the attribute simply ignore it and load the image normally, so there is no fallback to write and no broken experience to worry about.

The three values you need to know

Value Behaviour When to use it
lazy Defers the download until the image approaches the viewport Everything below the fold: grids, galleries, footers, related posts
eager Loads immediately, regardless of position Hero images, logos, anything visible on first paint
attribute omitted Browser default, equivalent to eager loading Acceptable for above-the-fold images, but being explicit is safer in a CMS
photo gallery website

The LCP trap: the images you must never lazy load

Largest Contentful Paint measures when the largest visible element in the viewport finishes rendering. On a design site, that element is almost always an image: the full-bleed hero, the first project thumbnail, the product shot. LCP is part of Core Web Vitals, and Core Web Vitals feed into Google’s page experience signals.

When you lazy load that element, you force the browser into a slower sequence:

  1. HTML is parsed, but the image is skipped by the preload scanner
  2. CSS and layout have to be computed
  3. Only then does the browser work out the image is in the viewport
  4. The request finally starts

You routinely add several hundred milliseconds to a full second of delay to your most important pixel. Google’s own guidance is blunt about this: lazy loading in-view content is detrimental and should be avoided.

How to decide what to exclude

Use these rules in order:

  1. Exclude anything inside the first viewport on mobile. Mobile is narrower and taller, so items that look below the fold on your 27-inch monitor may be fully visible on a phone. Test at 390 x 844 px.
  2. Exclude the LCP element, always. Identify it in DevTools (see below) rather than guessing.
  3. Exclude the first one or two items of a grid. On a portfolio grid, the top row is usually partially visible immediately.
  4. Exclude logos, above-the-fold icons and background-style images that form part of the first impression.
  5. Lazy load everything else. From the second screen down, defer it all.

Give your hero image a priority boost

Removing loading="lazy" from the hero is step one. Step two is telling the browser the image matters more than everything else competing for bandwidth:

<img src="/images/hero-2400.webp"
     srcset="/images/hero-800.webp 800w, /images/hero-1600.webp 1600w, /images/hero-2400.webp 2400w"
     sizes="100vw"
     alt="Pixelbright studio hero visual"
     width="2400"
     height="1350"
     loading="eager"
     fetchpriority="high">

Key points on that snippet:

  • fetchpriority="high" moves the hero to the front of the request queue. Use it on one image per page only, otherwise the signal loses all meaning.
  • width and height are mandatory on every image, lazy or not. They reserve layout space and protect your Cumulative Layout Shift score.
  • Never combine fetchpriority="high" with loading="lazy" on the same element. It is a contradiction, and the lazy behaviour wins.
  • Do not add decoding="async" to the LCP image; keep it for the deferred ones.

Is lazy loading bad for SEO? The honest answer

Native lazy loading on below-the-fold images is recommended by Google and is good for SEO. The risks come from how it is implemented, not from the concept. Here is where real damage occurs:

1. JavaScript-only lazy loading that hides the real URL

Old libraries put a placeholder in src and the real file in data-src. If the script fails, or if Googlebot does not trigger the scroll event, the image is never discovered and never indexed in Google Images. Native loading="lazy" keeps the real URL in src, so this problem disappears entirely. That is the single strongest reason to migrate off legacy scripts.

2. Content that only appears on scroll or on click

Googlebot does not scroll like a human; it renders with a very tall viewport. Lazy loading images is fine. Lazy loading text, links or whole sections behind a scroll event is risky. Keep crawlable content in the HTML.

3. Infinite scroll galleries with no paginated fallback

If your gallery loads more work as the user scrolls, make sure each batch is also reachable through real paginated URLs (/work/page/2/) with standard <a href> links. Otherwise most of your portfolio never enters the index.

4. Over-lazy-loading, the designer’s mistake

The most common issue we see on agency and portfolio sites: a global find-and-replace adds loading="lazy" to every single <img> in the template, hero included. The site looks optimised, the audit says otherwise.

photo gallery website

How to check your implementation in Chrome DevTools

Verification takes about five minutes. Do it before you ship, not after a ranking drop.

Step 1: confirm which element is your LCP

  1. Open the page in a Chrome incognito window (extensions distort measurements)
  2. Open DevTools, go to the Performance panel
  3. Set the device toolbar to a mobile size and throttling to Slow 4G plus 4x CPU slowdown
  4. Click the reload icon to record a page load
  5. In the timings track, hover the LCP marker. DevTools highlights the element and names it in the summary

Then inspect that element in the Elements panel. If you see loading="lazy" on it, you have found your problem.

Step 2: verify deferral actually happens

  1. Open the Network panel and filter by Img
  2. Reload without scrolling and count the requests. Only above-the-fold images should appear
  3. Scroll slowly and watch new image requests fire as sections come into view
  4. If every image loads at once, your attribute is missing, overwritten by a plugin, or cancelled by a JS library

Step 3: run Lighthouse locally

In the Lighthouse panel, run a mobile performance audit and look for these audits:

  • “Largest Contentful Paint image was lazily loaded” (fails if the hero is deferred)
  • “Defer offscreen images” (fails if you are not lazy loading enough)
  • “Properly size images” and “Serve images in next-gen formats”

A healthy image-heavy page passes the first and the second at the same time. That balance is the whole point.

photo gallery website

How to check in PageSpeed Insights

Lighthouse in DevTools uses your machine and your connection. PageSpeed Insights adds something more valuable: field data from real Chrome users (CrUX), when your page has enough traffic.

  1. Go to PageSpeed Insights and test the exact URL, not just the homepage
  2. Start with the Mobile tab. It is the one that reflects how most visitors experience the site
  3. Read “Discover what your real users are experiencing” first. Your LCP target is under 2.5 seconds at the 75th percentile
  4. Open the LCP element entry in the diagnostics to see exactly which image is being measured
  5. Expand the LCP breakdown: a large “Resource load delay” is the classic fingerprint of a lazy-loaded hero
  6. Re-test 24 to 48 hours after deploying, and keep an eye on the Core Web Vitals report in Search Console for field data over 28 days

Reading the LCP sub-parts

Sub-part What a high value usually means
Time to First Byte Server, hosting or caching issue, not an image issue
Resource load delay The hero is lazy loaded, injected by JavaScript, or set as a CSS background
Resource load duration File is too heavy: compress, convert to WebP or AVIF, add srcset
Element render delay Render-blocking CSS or fonts, or a reveal animation gating the image

Platform notes for image-heavy sites

  • WordPress: core adds loading="lazy" automatically and tries to skip the first images in the content, plus it applies fetchpriority="high" to the likely LCP image. It is not always right on custom themes and page builders. Check the rendered HTML of your hero manually.
  • Page builders and sliders: many output images as CSS backgrounds, which the browser discovers late. For a hero, prefer a real <img> element, or add a <link rel="preload" as="image"> in the head.
  • Webflow, Shopify, Squarespace: all offer a per-image loading setting. Set the hero to eager and leave the rest lazy.
  • Masonry and filterable grids: JS layout scripts sometimes rewrite src values. Re-check the first row after the script runs.
  • Animation libraries: fade-in-on-scroll effects applied to the hero can delay render even when the file loads fast. Exclude the first viewport from scroll reveals.
photo gallery website

Quick implementation checklist

  1. Identify the LCP element on mobile for each page template
  2. Set it to loading="eager" with fetchpriority="high"
  3. Add loading="lazy" and decoding="async" to every image below the first viewport
  4. Add explicit width and height to every image
  5. Serve WebP or AVIF with a proper srcset and sizes
  6. Write descriptive alt text, since Google Images still relies on it
  7. Keep real image URLs in src, never only in data-src
  8. List key images in an image sitemap if your gallery is large
  9. Verify in DevTools, then in PageSpeed Insights, then in Search Console field data

FAQ

Is lazy loading good or bad for SEO?

It is good for SEO when applied to below-the-fold images using the native loading attribute, because it improves load times without hiding content from crawlers. It becomes harmful when applied to in-view images, especially the LCP element, or when the real image URL is hidden behind JavaScript.

Does Google index lazy loaded images?

Yes, when you use native loading="lazy", because the actual file path stays in the src attribute and Googlebot renders with a very tall viewport. Images that only get a real URL after a user scroll event, through a custom script, may not be indexed.

Should I lazy load the hero image?

No. The hero is almost always the Largest Contentful Paint element. Lazy loading it delays discovery and directly worsens your LCP score.

Why is loading=”lazy” not working on my site?

The usual causes are: the image is already inside or near the viewport so the browser loads it immediately, a JavaScript library or optimisation plugin is overwriting the attribute, the image is a CSS background rather than an <img> element, or the page is short enough that every image is within the browser’s loading threshold.

Which is better, lazy loading or eager loading?

Neither is better overall; they serve different positions on the page. Eager loading is correct for everything visible on first paint, lazy loading is correct for everything else. A well-built image-heavy page uses both. This guide goes deeper on it.

Does lazy loading help Core Web Vitals?

It helps indirectly by freeing bandwidth for critical resources, which can improve LCP. It does not improve LCP by itself, and it hurts LCP if applied to the measured element. It also has no direct effect on Interaction to Next Paint, though reducing decode work on heavy pages can help responsiveness.

Can I lazy load videos and iframes too?

Yes. The loading="lazy" attribute works on <iframe> elements, which is particularly useful for embedded players and maps. For self-hosted video, use preload="none" with a lightweight poster image.

Need an audit on your image-heavy build?

If your portfolio or e-commerce site is visually strong but slow to paint, the fix is usually a handful of attributes in the right places rather than a rebuild. The Pixelbright team can audit your templates, pinpoint the LCP element on each one, and ship a loading strategy that keeps the design intact. The piece Lazy Loading Techniques For Images, Videos, And Iframes makes a good next read.

Leave a Comment