Technical Guides
Lazy Loading Images with an Image CDN - The Practical Guide
Lazy loading delays below-the-fold images. An image CDN resizes, compresses, and converts the images that do load. Here is how to use both without hurting LCP.
By Sunny Kumar · Editor
Lazy loading and image CDNs solve different problems. Lazy loading controls when images download. An image CDN controls how big those images are and where they are served from. Used together, they can make image-heavy pages much lighter. Used badly, they can delay your hero image and hurt LCP.
TL;DR: Add loading="lazy" only to images below the first viewport. Do not lazy-load your hero image, product lead image, or any likely LCP image. Give the LCP image normal eager loading, correct dimensions, and usually fetchpriority="high". Then serve all images through an image CDN for resizing, WebP/AVIF conversion, compression, and caching. Lazy loading saves unnecessary downloads. The CDN makes necessary downloads smaller.
Fast Setup
What to lazy-load, what to keep eager, and where the CDN fits.
LCP Rule
The most common lazy-loading mistake.
Implementation
HTML examples, srcset, sizes, and CDN URLs.
SEO
Googlebot, native lazy loading, Intersection Observer, and scroll traps.
The Practical Rule
If the image is visible before the user scrolls, do not lazy-load it. If the image is below the fold, lazy-load it. If the image is large, resize and convert it through a CDN either way.
CDN Shortcut
BunnyCDN Optimizer can resize and convert images to modern formats while your HTML controls lazy loading. Use coupon THEWPX for $5 free credit through this BunnyCDN signup link.
What Is the Right Lazy Loading Setup?
Use this as the default pattern.
| Image type | Loading behavior | Why |
|---|---|---|
| Hero image / likely LCP image | Eager, often fetchpriority="high" | It must start loading early |
| Site logo | Eager | Small and visible immediately |
| First product image | Eager | Usually important above the fold |
| Images below the first viewport | loading="lazy" | Avoid unnecessary downloads |
| Gallery images after the first visible row | loading="lazy" | User may never scroll to them |
| Footer, related posts, comments, ads | loading="lazy" | Low initial priority |
| CSS background images | Custom handling | loading="lazy" does not apply to CSS backgrounds |
The image CDN is separate from that decision.
The CDN should handle:
- resizing by viewport
- WebP/AVIF conversion
- JPEG/PNG fallback
- compression
- caching
- delivery from a nearby edge location
Lazy loading does not optimize an image file. It only delays the request. A 2 MB image is still a 2 MB image when it finally loads. That is why lazy loading and an image CDN belong together.
Why Do Lazy Loading and Image CDNs Work Well Together?
Lazy loading reduces the number of images downloaded during initial page load.
An image CDN reduces the bytes for the images that do download.
That is the compound benefit.
Example: a long article has 20 images. The first viewport shows two images. Lazy loading means the browser does not download the other 18 immediately. The image CDN then makes those first two images smaller by resizing them and serving WebP or AVIF when supported.
This is more reliable than only doing one side:
- Lazy loading without resizing can still load huge images later.
- A CDN without lazy loading can still download images the user never sees.
- Both together reduce initial contention and long-page waste.
web.dev's lazy-loading guide explains the performance reason clearly: deferring offscreen images can reduce bandwidth contention for critical resources, which can help LCP on poor network connections.
Why Should You Not Lazy Load the LCP Image?
Because the LCP image is the image you need fastest.
loading="lazy" tells the browser it can delay the request until the image is near the viewport. That is good for below-fold images. It is bad for a hero image already visible on first load.
web.dev's LCP optimization guide calls this out directly: you can delay your LCP image by setting loading="lazy" on the <img> element.
Use this pattern instead:
<img
src="https://cdn.example.com/images/hero.jpg"
alt="Modern living room with blue sofa"
width="1200"
height="700"
fetchpriority="high"
/>
Then lazy-load below-fold images:
<img
src="https://cdn.example.com/images/product-detail.jpg"
alt="Close-up of blue sofa fabric"
width="900"
height="600"
loading="lazy"
/>
Do not combine fetchpriority="high" and loading="lazy" on the same image. They send opposite signals.
MDN notes that fetchpriority is baseline across latest devices and browsers since October 2024. It is still a hint, not a magic fix. The image must also be discoverable early in the HTML.
How Do You Implement Lazy Loading with a CDN?
Basic HTML Setup
For most websites, native lazy loading is enough.
<!-- Above-the-fold image: eager -->
<img
src="https://cdn.example.com/images/hero.jpg"
alt="Homepage hero image"
width="1200"
height="700"
fetchpriority="high"
/>
<!-- Below-the-fold image: lazy -->
<img
src="https://cdn.example.com/images/gallery-01.jpg"
alt="Gallery image of the product from the side"
width="900"
height="600"
loading="lazy"
/>
The CDN URL can still point to a .jpg or .png source. If your CDN supports automatic format negotiation, it can serve WebP or AVIF to browsers that support those formats. The browser does not care whether lazy-loaded content comes from your domain or a CDN URL.
Responsive CDN Images
Lazy loading should not replace responsive images. Use both.
<img
srcset="
https://cdn.example.com/images/photo.jpg?w=480 480w,
https://cdn.example.com/images/photo.jpg?w=800 800w,
https://cdn.example.com/images/photo.jpg?w=1200 1200w
"
sizes="(max-width: 640px) 100vw, (max-width: 1200px) 80vw, 960px"
src="https://cdn.example.com/images/photo.jpg?w=800"
alt="Responsive product image"
width="1200"
height="800"
loading="lazy"
/>
The browser picks the right size. Lazy loading decides when to request it. The CDN generates or serves the resized variant.
With a Picture Element
If you generate formats yourself, use <picture>.
<picture>
<source srcset="/images/article.avif" type="image/avif" />
<source srcset="/images/article.webp" type="image/webp" />
<img
src="/images/article.jpg"
alt="Article illustration"
width="1200"
height="800"
loading="lazy"
/>
</picture>
If a CDN handles format negotiation through the Accept header, you may not need a <picture> element for format selection. You still need width, height, alt text, and the right loading behavior.
Should You Use Intersection Observer?
Use native loading="lazy" first.
Use Intersection Observer only when you need custom behavior, such as:
- lazy-loading CSS background images
- swapping a blurred placeholder for a full image
- custom preload distance
- animation when an image enters the viewport
- loading components, not just images
Example:
<img
src="https://cdn.example.com/images/photo.jpg?w=40&q=20"
data-src="https://cdn.example.com/images/photo.jpg?w=900"
alt="Product image"
width="900"
height="600"
class="lazy-img"
/>
<script>
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
});
}, { rootMargin: '300px 0px' });
document.querySelectorAll('.lazy-img').forEach((img) => observer.observe(img));
</script>
Keep this simple. If native lazy loading works, do not add JavaScript just because you can.
How Do You Prevent Layout Shift?
Always include dimensions.
Lazy loading becomes ugly when the browser does not know how much space to reserve. The image loads later, expands from nothing, and pushes content down. That is CLS.
Use width and height:
<img
src="https://cdn.example.com/images/chart.png"
alt="Chart showing image payload reduction"
width="900"
height="500"
loading="lazy"
/>
For responsive layouts, CSS can preserve the ratio:
.article-image {
width: 100%;
height: auto;
aspect-ratio: 9 / 5;
object-fit: cover;
}
Do not skip this. Lazy loading does not cause CLS by itself. Missing dimensions do.
Does Lazy Loading Hurt SEO?
Native lazy loading is SEO-safe when implemented normally.
Google Search Central recommends making sure lazy-loaded content loads whenever it is visible in the viewport. It also warns against relying on user actions such as scrolling or clicking, because Google Search does not interact with your page that way.
Safe patterns:
- native
loading="lazy" - Intersection Observer
- real
srcattributes where possible - crawlable image URLs
- useful
alttext - visible content loading without clicks
Risky patterns:
- scroll-event-only lazy loading
- images hidden behind interaction
- images only stored in JavaScript state
- missing fallback content
- blank placeholders that never resolve in rendered HTML
After a major lazy-loading change, use Search Console's URL Inspection tool and check the rendered screenshot. If important images are missing there, fix the implementation.
For CDN-specific SEO risks, read can image CDNs hurt SEO?.
WordPress Lazy Loading Notes
WordPress added native image lazy loading years ago and has since refined it to avoid lazy-loading likely above-the-fold images.
For most modern WordPress sites, the main job is not adding another lazy-loading plugin. The main job is checking that:
- the LCP image is not lazy-loaded
- image dimensions are present
- CDN URL rewriting does not break
srcset - WebP/AVIF conversion preserves fallbacks
- product/gallery images below the fold are lazy-loaded
If you install multiple optimization plugins, check the final HTML. Duplicate lazy loading scripts, broken srcset, and lazy-loaded hero images are common plugin-stack problems.
For WordPress-specific CDN options, see best image CDNs for WordPress.
What Should You Test?
Do not assume the setup is correct because the page "looks fine."
Check these after implementation:
| Test | What to verify |
|---|---|
| Chrome DevTools Network tab | Below-fold images do not load on initial render |
| PageSpeed Insights | LCP image is not lazy-loaded |
| Lighthouse / PSI CLS | Images have dimensions or reserved aspect ratio |
| Rendered HTML | srcset, sizes, CDN URLs, and alt text are present |
| Search Console URL Inspection | Important images appear in Google's rendered screenshot |
| Real device scroll test | No blank gaps while scrolling normally |
Also test the page on a slow network profile in DevTools. Lazy loading that feels fine on fiber can still show blank gaps on weak mobile connections if the preload distance is too tight or images are too heavy.
Common Mistakes
1. Lazy-loading the hero image
This is the biggest one. The LCP image should load early.
2. Using lazy loading instead of resizing
Lazy loading delays the image. It does not shrink it. Use CDN resizing too.
3. Missing width and height
This creates layout shift when the image finally loads.
4. Using JavaScript when native lazy loading is enough
Extra JavaScript can add failure modes. Use it only for custom behavior.
5. Breaking srcset during CDN rewriting
Make sure your CDN plugin or template rewrites every variant consistently.
6. Lazy-loading images that are only slightly below the fold without testing
If users scroll immediately and see blanks, tune the approach or let the browser's native lazy loading handle the threshold.
Frequently Asked Questions
Should I lazy load every image?
No. Lazy-load below-the-fold images only. Do not lazy-load your hero image, logo, first product image, or any image visible in the first viewport.
Does lazy loading work with image CDNs?
Yes. Lazy loading controls when the browser requests the image. The image CDN controls resizing, compression, format conversion, caching, and delivery once the request happens.
Does lazy loading hurt LCP?
It can if you lazy-load the LCP image. Keep the LCP image eager, make it discoverable early, add dimensions, and consider fetchpriority="high". Lazy-load images below the fold.
Does lazy loading hurt SEO?
Native lazy loading does not hurt SEO when implemented correctly. Avoid scroll-event-only implementations and make sure important images load when visible in the viewport. Verify with Search Console's rendered screenshot.
Can I lazy load CSS background images?
Not with the native loading="lazy" attribute. That attribute works on images and iframes. For CSS backgrounds, use Intersection Observer or change the design so important images are real <img> elements.
Should I use fetchpriority high on lazy-loaded images?
No. fetchpriority="high" and loading="lazy" conflict. Use high priority for the LCP image or other critical above-the-fold image. Use lazy loading for below-the-fold images.
Do I still need lazy loading if I use an image CDN?
Yes, on long or image-heavy pages. The CDN makes images smaller and closer. Lazy loading prevents downloads for images the user never reaches. They solve different parts of the problem.
Summing Up!
Lazy loading is simple when you keep the rule clean: visible images load eagerly, below-fold images lazy-load. The LCP image should never be delayed just because a plugin added loading="lazy" everywhere.
An image CDN makes the setup stronger because it resizes, compresses, converts, and caches the images that do load. Lazy loading saves requests. The CDN saves bytes.
For most sites, the right setup is: eager LCP image with dimensions and optional fetchpriority="high", lazy-loaded below-fold images, responsive srcset/sizes, and CDN format conversion for WebP/AVIF delivery. Start with best image CDNs or the quick startup guide if you want a simple implementation path.