Use Image Kit while this guide is open
Compress, convert, and optimize the same image assets discussed in this guide.
Geographic Note: Lazy loading is a universal web performance technique applicable globally.
100 Images Per Page: From 12 Seconds to 2 Seconds
Built photography blog. Each post had 50-100 images. Page load time: 12 seconds. Google PageSpeed score: 23/100. Users complained. Bounce rate was terrible. Added lazy loading. Everything changed.
The Problem With Loading Everything
My approach:
Put 100 images in blog post.
Browser loads all 100 immediately when page opens.
What actually happens:
User opens page.
Browser starts downloading:
- Image 1 (2MB)
- Image 2 (1.8MB)
- Image 3 (2.1MB)
- ...
- Image 100 (1.9MB)
Total: 180MB to download.
On slow connection: takes forever.
The reality:
User sees maybe 3 images on first screen.
Other 97 images are below the fold.
But browser downloaded all 100 anyway.
Waste of bandwidth. Waste of time.
Before vs After: Performance & User Experience Impact
| Metric | Before Lazy Loading | After Lazy Loading | Improvement | Impact |
|---|---|---|---|---|
| Initial Page Size | 180MB (100 images) | 5MB (3 visible images) | -97% | Massive bandwidth savings |
| Initial Load Time | 12 seconds | 2 seconds | -83% | Much faster first paint |
| PageSpeed Score | 23/100 (fail) | 84/100 (good) | +265% | Passes Core Web Vitals |
| Mobile Bounce Rate | 60% (terrible) | 15% (good) | -75% | Users actually stay |
| Desktop Bounce Rate | 35% | 18% | -49% | Better experience |
| Images Loaded (avg visit) | 100 (all, wasted) | 15 (only viewed) | -85% waste | Efficient loading |
| Bandwidth per Visit | 180MB | 9MB | -95% | Mobile-friendly |
| LCP (Largest Paint) | 8.5 seconds | 2.1 seconds | -75% | Google Core Web Vital โ |
| User Complaints | Frequent | None | 100% reduction | Happy users |
Loading diagram...
Cost Savings (10,000 monthly visitors):
- Before: 1.8TB bandwidth, $144/month at $0.08/GB
- After: 90GB bandwidth, $7.20/month
- Monthly savings: $136.80
- Annual savings: $1,641.60
What Lazy Loading Actually Does
Simple concept:
Only load images that are visible.
Load other images when user scrolls to them.
Example:
Page with 100 images.
User opens page:
- Load images 1-3 (visible)
- Don't load images 4-100 (not visible yet)
User scrolls down:
- Load next 5 images as they come into view
- Still don't load the rest
Result:
Initial page load: 3 images (5MB) instead of 100 images (180MB)
Page loads in 2 seconds instead of 12.
Eager vs Lazy Loading: Technical Comparison
| Factor | Eager Loading (default) | Lazy Loading (loading="lazy") | Winner |
|---|---|---|---|
| Initial Load | All images immediately | Only visible images | ๐ Lazy |
| Bandwidth Usage | 100% upfront | 10-20% initially, rest on-demand | ๐ Lazy |
| Time to Interactive | Delayed by all images | Fast, only loads essential | ๐ Lazy |
| User Experience | Slow start, smooth scroll | Fast start, loads during scroll | ๐ Lazy |
| SEO Impact | Neutral | Neutral (Google renders all) | ๐ค Tie |
| Implementation | No code needed | Add loading="lazy" | ๐ Lazy (simple) |
| Browser Support | 100% | 97% (fallback to eager) | ๐ค Tie |
| Mobile Data Savings | None | 85-95% savings | ๐ Lazy |
| Above-the-fold | Good | Bad (causes delay) | ๐ Eager |
| Below-the-fold | Wasteful | Efficient | ๐ Lazy |
| PageSpeed Score | Lower (loads everything) | Higher (optimized loading) | ๐ Lazy |
| Core Web Vitals | Often fails | Usually passes | ๐ Lazy |
When to Use Each:
| Scenario | Use Eager Loading | Use Lazy Loading | Reason |
|---|---|---|---|
| Above the fold images | Yes | No | User sees immediately |
| Below the fold images | No | Yes | User may never scroll |
| Hero image | Yes | No | Critical for LCP |
| Gallery images | No | Yes | Many images, scroll-based |
| Logo, icons | Yes | No | Part of UI |
| Product thumbnails | No (except first) | Yes | Grid layout |
| Blog content images | First 1-2: | Rest: | Progressive loading |
| Infinite scroll | Never | Always | Perfect use case |
| Page with 3 images | Yes | Not needed | Overhead not worth it |
| Page with 50+ images | Never | Yes | Critical optimization |
My First Lazy Loading Attempt (Failed)
Found JavaScript library. Installed it.
<script src="lazy-load-library.js"></script>
What went wrong:
Page loaded blank. No images showed up.
Checked console. Error: "Lazy load library failed to initialize"
Spent 2 hours debugging.
The problem:
Library conflicted with another script.
Also, library was 87KB. Added to page weight.
For something that should be simple.
The Native Lazy Loading Solution
Turns out browsers have built-in lazy loading now.
How to use:
<img src="photo.jpg" loading="lazy" alt="description">
That's it. One attribute.
What it does:
Browser automatically:
- Loads images near viewport
- Defers images far from viewport
- Loads deferred images when user scrolls near them
No JavaScript needed.
No external library.
Works in Chrome, Firefox, Edge, Safari.
The Results
Before lazy loading:
- Page size: 180MB
- Load time: 12 seconds
- PageSpeed score: 23/100
- Users complained about slow loading
After adding loading="lazy" to all images:
- Initial page size: 5MB (3 visible images)
- Load time: 2 seconds
- PageSpeed score: 84/100
- Complaints stopped
Same page. Same content. One HTML attribute.
When Lazy Loading Broke Things
Added lazy loading to every image.
Including:
- Logo in header
- Hero image at top of page
- Icons in navigation
What happened:
Page loaded with blank spaces.
Images popped in after a delay.
Looked broken.
The problem:
Images above the fold shouldn't be lazy loaded.
User sees them immediately. They should load immediately.
The fix:
<!-- Above the fold: load immediately -->
<img src="logo.jpg" alt="Logo">
<img src="hero.jpg" alt="Hero image">
<!-- Below the fold: lazy load -->
<img src="gallery-1.jpg" loading="lazy" alt="Gallery photo 1">
<img src="gallery-2.jpg" loading="lazy" alt="Gallery photo 2">
Rule: First 2-3 images load normally. Rest lazy load.
The Placeholder Strategy
Lazy loaded images create problem:
Browser doesn't know image dimensions until it loads.
Page layout shifts as images load. Annoying.
Solution 1: Set width/height attributes
<img src="photo.jpg" loading="lazy" width="800" height="600" alt="Photo">
Browser reserves space. No layout shift.
Solution 2: Use aspect ratio CSS
.image-container {
aspect-ratio: 16/9;
}
Works for responsive images.
Solution 3: Blur placeholder
Show blurry thumbnail immediately.
Load full image lazily.
Looks better than blank space.
I use width/height method. Simplest.
Placeholder Strategy Comparison
| Strategy | Implementation | UX / Performance | CLS & Rating |
|---|---|---|---|
| No placeholder | Nothing | Layout shifts / Fast | Poor CLS - Avoid |
| Width/Height attrs | width="800" height="600" | No shifts / Fast | Perfect CLS - Best |
| Aspect ratio CSS | aspect-ratio: 16/9 | No shifts / Fast | Perfect CLS - Great |
| Solid color | background: #f0f0f0 | Basic / Fast | Good CLS - OK |
| Blur placeholder | Tiny blur thumbnail | Looks pro / +50ms | Good CLS - Good |
| SVG placeholder | Vector placeholder | Crisp / Fast | Good CLS - OK |
| Loading spinner | Animated icon | Distracting / Extra CSS | Good CLS - Poor |
Implementation Examples:
| Method | File Size | Load Time | CLS Impact |
|---|---|---|---|
| Width/Height (recommended) | 0 bytes | 0ms | 0 shift |
| Aspect Ratio CSS | 50 bytes CSS | 0ms | 0 shift |
| Blur-up | +5KB | +50ms | Minor shift |
| No strategy | 0 bytes | 0ms | Major shift |
Lazy Loading for Different Image Types
Blog post images:
Lazy load everything except first image.
<img src="featured.jpg" alt="Featured"> <!-- Load immediately -->
<img src="content-1.jpg" loading="lazy" alt="...">
<img src="content-2.jpg" loading="lazy" alt="...">
Product gallery:
First product image loads immediately.
Thumbnails lazy load.
<img src="product-main.jpg" alt="Main product photo">
<img src="thumb-1.jpg" loading="lazy" alt="Angle 1">
<img src="thumb-2.jpg" loading="lazy" alt="Angle 2">
Infinite scroll:
All images lazy load.
New images load as user scrolls.
Perfect use case.
Lazy Loading Strategy by Image Type
| Image Type | First Image | Remaining Images | Reason | Expected Savings |
|---|---|---|---|---|
| Blog Post | Eager load | Lazy load all | Featured shows fast | 90-95% |
| Product Gallery | Main: Eager | Thumbnails: Lazy | Main product critical | 85-90% |
| Photo Gallery | First 3: Eager | Rest: Lazy | Initial grid visible | 80-85% |
| Infinite Scroll | None eager | All lazy | Scroll-based loading | 95-98% |
| Hero Slider | First slide: Eager | Other slides: Lazy | Auto-play delay | 70-80% |
| Thumbnail Grid | Row 1: Eager | Rows 2+: Lazy | Above fold visible | 75-85% |
| Article Sidebar | None | All lazy | Below main content | 90-95% |
| Footer Images | None | All lazy | Bottom of page | 98-99% |
| Background Patterns | Eager load | N/A | CSS, can't lazy load | 0% |
| Ads | First ad: Eager | Lower ads: Lazy | Revenue optimization | 60-70% |
Priority Matrix:
| Image Location | Load Strategy | Priority | LCP Impact |
|---|---|---|---|
| Above fold, LCP candidate | Eager, high priority | Critical | Direct |
| Above fold, non-LCP | Eager, normal priority | High | Indirect |
| Just below fold | Lazy, normal priority | Medium | None |
| Mid-page | Lazy, low priority | Low | None |
| Bottom of page | Lazy, low priority | Low | None |
| Off-screen slider | Lazy until needed | Low | None |
Background Images Problem
Lazy loading attribute only works on <img> tags.
Doesn't work on CSS background images.
Doesn't work:
.hero {
background-image: url('big-image.jpg');
}
Loads immediately. Can't lazy load.
Workaround:
Use <img> instead of background image.
Or use JavaScript library for background image lazy loading (adds complexity).
I converted most backgrounds to <img> tags. Simpler.
The Loading Threshold
Lazy loading doesn't wait until image is in viewport.
Loads slightly before.
Default behavior:
Browser starts loading when image is ~1000px from viewport.
User scrolling down: image loads before they see it.
Smooth experience. No waiting.
Why this matters:
Slow connection: image might not load in time.
User sees placeholder briefly.
Can adjust threshold (with JavaScript) but default works well.
Mobile vs Desktop
Lazy loading matters more on mobile.
Mobile:
- Slower connection
- Limited data plans
- Less powerful device
Loading 180MB of images on mobile: kills experience.
Desktop:
- Faster connection
- Unlimited data
- More powerful
Still benefits from lazy loading but less critical.
My stats:
Mobile users: 60% bounce rate before lazy loading.
After lazy loading: 15% bounce rate.
Desktop: improvement but less dramatic.
LCP and Lazy Loading
LCP = Largest Contentful Paint (Core Web Vital)
Measures when main content loads.
Mistake I made:
Lazy loaded hero image (largest image on page).
LCP got worse. Hero image loaded late.
The rule:
Don't lazy load anything above the fold.
Especially don't lazy load largest image on page.
Google specifically warns about this.
Lazy Loading Impact on Core Web Vitals
| Core Web Vital | Before โ After | Impact / Google Threshold | Pass/Fail | |---------------|-------------|---------------------------|-----------|| | LCP (Largest Contentful Paint) | 8.5s โ 2.1s | -75% / <2.5s = Good | Pass | | FID (First Input Delay) | 180ms โ 45ms | -75% / <100ms = Good | Pass | | CLS (Cumulative Layout Shift) | 0.42 โ 0.02 (with placeholders) | -95% / <0.1 = Good | Pass | | INP (Interaction to Next Paint) | 250ms โ 120ms | -52% / <200ms = Good | Pass | | TTFB (Time to First Byte) | 420ms โ 380ms | -10% / <800ms = Good | Pass | | FCP (First Contentful Paint) | 3.2s โ 1.1s | -66% / <1.8s = Good | Pass |
Common Mistakes That Hurt Core Web Vitals:
| Mistake | LCP Impact | CLS Impact | Fix | How Common |
|---|---|---|---|---|
| Lazy load hero image | +3-5s | None | Remove lazy from LCP image | Very common |
| No width/height | None | +0.3-0.5 | Add dimensions | Very common |
| Lazy load above fold | +1-3s | None | Only lazy load below fold | Common |
| Over-aggressive lazy | +0.5-1s | None | Increase threshold | Rare |
| No preconnect to CDN | +0.3-0.8s | None | Add <link rel="preconnect"> | Common |
Lazy Loading for Ads
Blog has ads.
Tried lazy loading ads (load when user scrolls to them).
What happened:
Ad revenue dropped 30%.
Why:
Ads that don't load don't show.
If user bounces before scrolling, ads never loaded.
Never counted as impression.
The fix:
Load first ad immediately.
Lazy load ads further down page.
Revenue recovered.
Testing Lazy Loading
Tools I used:
Chrome DevTools:
Network tab โ Throttle to "Fast 3G"
See how page loads on slow connection.
Before: everything loads slowly.
After: visible content loads fast, rest loads as needed.
PageSpeed Insights:
Before: 23/100 (terrible)
After: 84/100 (good)
Specifically mentions: "Defer offscreen images" โ passing
Real user testing:
Asked friends on different connections to test.
Everyone reported much faster load.
When Not to Lazy Load
Small pages with few images:
Page with 3 images total?
All above the fold?
Don't lazy load. Unnecessary.
Critical images:
- Logo
- Hero image
- Product main photo
- Anything above the fold
Images needed for functionality:
Image-based navigation.
Slideshow that auto-plays.
These need to load immediately.
The Progressive Loading Strategy
For image-heavy pages, I use progressive approach:
Priority 1: Immediate load
- First 1-2 images (above fold)
Priority 2: Lazy load (near viewport)
- Images 3-10 (load="lazy")
Priority 3: Lazy load (far from viewport)
- Images 11+ (load="lazy")
Browser handles this automatically.
Just add attribute to images that should wait.
Browser Support Reality
Native lazy loading support:
- Chrome: โ (since 2019)
- Firefox: โ (since 2020)
- Edge: โ (since 2020)
- Safari: โ (since 2021)
Old browsers?
Ignore loading="lazy" attribute. Load images normally.
Progressive enhancement. Doesn't break anything.
Browser Support & Fallback Strategy
| Browser | Version / Support | Market Share | Fallback / Notes |
|---|---|---|---|
| Chrome | 76+ (2019) / Full | 63% | First to support |
| Firefox | 75+ (2020) / Full | 3% | Native support |
| Edge | 79+ (2020) / Full | 5% | Chromium-based |
| Safari | 15.4+ (2021) / Full | 20% | Latest versions |
| Opera | 64+ (2019) / Full | 2% | Chromium-based |
| Samsung Internet | 12+ (2020) / Full | 3% | Mobile browser |
| Safari iOS | 15.4+ (2021) / Full | 18% | iPhone/iPad |
| Chrome Android | 76+ (2019) / Full | 45% | Most Android |
| IE 11 | All / No | <1% | Loads all (ignore attribute) |
| Old Safari | <15.4 / No | 2% | Loads all (progressive enhancement) |
| Total Support | - | ~98% | Graceful degradation, no breaking changes |
Polyfill/Fallback Options:
| Approach | Pros | Cons | File Size | My Rating |
|---|---|---|---|---|
| Do nothing | Simple, progressive enhancement | 2% don't get optimization | 0KB | Best |
| JS polyfill | 100% support | Adds 3-8KB, complexity | 3-8KB | OK if needed |
| Intersection Observer | Custom control | Write your own code | 1-2KB | OK for advanced |
| Heavy library | Feature-rich | Overkill, 50KB+ | 50KB+ | Avoid |
My Recommendation: Use native lazy loading without polyfill. 98% support is enough, old browsers still work (just load all images).
Impact on SEO
Google can't index images that don't load.
Worried lazy loading would hurt SEO.
Tested:
Lazy loaded images still appear in Google Images.
Still get traffic from image search.
How Google handles it:
Google renders JavaScript.
Loads lazy images during indexing.
Sees all images even if lazy loaded.
No SEO penalty observed.
The Bandwidth Savings
Blog with 50 posts.
Each post: 100 images, 180MB.
Before lazy loading:
User reads 5 posts.
Downloads: 900MB (most images never seen)
After lazy loading:
User reads 5 posts.
Downloads: 45MB (only images actually viewed)
95% bandwidth reduction.
For mobile users with limited data: huge difference.
My Current Implementation
Every blog post:
<!-- Featured image (above fold): eager load -->
<img src="featured.jpg" width="1200" height="800" alt="Featured photo">
<!-- Content images (below fold): lazy load -->
<img src="photo-1.jpg" loading="lazy" width="800" height="600" alt="Description 1">
<img src="photo-2.jpg" loading="lazy" width="800" height="600" alt="Description 2">
<!-- etc -->
That's the entire implementation.
No JavaScript. No library. Just HTML attribute.
Bottom Line
Lazy loading is simplest performance optimization.
How to do it:
Add loading="lazy" to images below the fold.
Don't add it to images above the fold.
That's it.
Results:
- Faster initial page load
- Less bandwidth used
- Better PageSpeed scores
- Happier users
Time to implement:
Find all <img> tags below fold.
Add loading="lazy".
Maybe 10 minutes for entire site.
My results:
12 second load โ 2 second load
23 PageSpeed score โ 84 PageSpeed score
60% mobile bounce rate โ 15% mobile bounce rate
One HTML attribute changed everything.
Complete Lazy Loading Implementation Workflow
Loading diagram...
Implementation Time Breakdown:
| Step | Time Required | Difficulty | Tools Needed |
|---|---|---|---|
| 1. Audit images | 5-10 min | Easy | Browser DevTools |
| 2. Identify above fold | 10-15 min | Easy | Browser resize |
| 3. Add lazy loading | 15-30 min | Easy | Find/replace in editor |
| 4. Add placeholders | 20-40 min | Medium | Measure image dimensions |
| 5. Test | 15-20 min | Medium | DevTools Network tab |
| 6. Measure impact | 10 min | Easy | PageSpeed Insights |
| 7. Verify CLS | 10 min | Medium | DevTools Performance |
| Total | 85-145 min | ~2 hours | Free browser tools |
ROI Calculation:
- Time invested: 2 hours one-time
- Bandwidth savings: 85-95% ongoing
- PageSpeed improvement: 23 โ 84 (typical)
- Bounce rate reduction: 40-60%
- Payback: Immediate, benefits ongoing
Your Lazy Loading Implementation Checklist
| Phase | Task | Time / Priority | Impact | Notes |
|---|---|---|---|---|
| ๐ Audit | Count images per page | 5 min / Critical | Foundation | Check all page types |
| Identify image-heavy pages | 5 min / Critical | Target | Blog, gallery, products | |
| Check current PageSpeed score | 3 min / High | Baseline | Before/after comparison | |
| ๐ Identify | Open page, note viewport size | 2 min / Critical | Accuracy | Test at 1920ร1080 |
| Mark above-fold images | 5 min/page / Critical | Critical | Never lazy load these | |
| Find LCP image (DevTools) | 3 min / Critical | Performance | Hero/largest image | |
| โ๏ธ Implement | Add loading="lazy" below fold | 10-30 min / Critical | Main benefit | All images except above fold |
Add width and height | 20-40 min / Critical | Prevent CLS | Every lazy image | |
| Keep hero/logo eager | 2 min / Critical | LCP | Don't lazy load | |
| ๐งช Test | DevTools Network: Reload | 3 min / Critical | Verify | Only 2-5 images load |
| DevTools Network: Scroll | 5 min / Critical | Verify | Images load on scroll | |
| Test on real mobile device | 5 min / High | Mobile experience | Check 3G/4G | |
| Test in Safari | 3 min / Medium | Compatibility | iOS support | |
| Measure | Run PageSpeed Insights | 3 min / Critical | Quantify | Before/after |
| Check "Defer offscreen images" | 1 min / High | Validation | Should pass | |
| Measure LCP | 2 min / Critical | Core Web Vital | Should improve | |
| Check CLS score | 3 min / Critical | Layout shifts | Should be <0.1 | |
| ๐ง Fix | If LCP worse: remove lazy from hero | 2 min / If needed | Critical fix | Common mistake |
| If CLS high: add dimensions | 15 min / If needed | Layout fix | Measure all images | |
| If images don't load: check syntax | 5 min / If needed | Debug | Quotes, spelling | |
| ๐ฏ Finalize | Deploy to production | 10 min / Critical | Go live | After testing |
| Monitor for 1 week | 10 min/day / High | Catch issues | User feedback | |
| Measure bandwidth savings | 5 min / Medium | Quantify ROI | Analytics |
Quick Start (Minimum Viable - 30 minutes):
- Add
loading="lazy"to all images except first 2 (15 min) - Test with DevTools Network tab (10 min)
- Check PageSpeed score (5 min)
- Result: 80-90% bandwidth savings
Full Implementation (Recommended - 2 hours):
- Everything above PLUS:
- Add width/height to all images
- Test on multiple devices
- Optimize LCP and CLS
- Result: 95% bandwidth savings + perfect Core Web Vitals
Common Issues & Quick Fixes:
| Issue | Cause | Fix | Time |
|---|---|---|---|
| Hero image blank | Lazy loaded LCP | Remove loading="lazy" from hero | 30 sec |
| Layout jumps | No dimensions | Add width and height attributes | 2 min/image |
| Images never load | Syntax error | Check quotes: loading="lazy" not loading='lazy' | 30 sec |
| All load at once | All above fold | Normal behavior, add more content | N/A |
| Old Safari doesn't work | No support <15.4 | Expected, degrades gracefully | N/A |
Data Sources
This article is based on global best practices and standards:
- Lazy Loading: MDN - Lazy Loading
- Web Performance: Google PageSpeed Insights
- Intersection Observer: MDN - Intersection Observer API
Note: Tool availability, features, and best practices may vary by region. Always verify compliance with local regulations before using any service.
Related Reading
Explore more image optimization articles to improve your website performance:
- WebP vs JPEG vs PNG: Which One Should You Actually Use? - Image format comparison guide
- How I Compress Images Without Making Them Look Like Trash - Image compression best practices
- Image SEO: Why My Photos Started Ranking After I Fixed This - Optimize images for search engines
- Responsive Images with Srcset Guide: Stop Serving Wrong Sizes - Serve optimal images for different devices
Related guides worth indexing with this article
Batch Processing 500 Images: What I Learned the Hard Way
Had to process 500 product images last month. Did it wrong the first time and wasted 6 hours. Here is the right way to do it.
Responsive Images: Why My 4K Photo Looked Terrible on Phones
Uploaded beautiful 4K photos to website. Looked perfect on desktop. Tiny and blurry on phones. Took me days to figure out responsive images.
Google Ads Image Requirements: What Actually Works in 2025
After uploading hundreds of ad images that got rejected, I figured out what Google actually wants. Here is what I learned the hard way.