Lazy Loading
Lazy loading is a performance optimization strategy that delays the loading of resources (images, videos, scripts, components) until they are actually needed. Instead of loading everything upfront, content is loaded on-demand as users interact with the page, significantly improving initial page load times and reducing bandwidth consumption.
Why Use Lazy Loading?
Performance Benefits
- Faster initial page load - Only critical content loads first
- Reduced bandwidth usage - Only requested resources download
- Lower server load - Fewer simultaneous requests
- Improved Core Web Vitals - Better LCP and INP scores
- Better mobile experience - Crucial for slower connections
Types of Lazy Loading
Image Lazy Loading
<!-- Native lazy loading (modern browsers) -->
<img src="image.jpg" loading="lazy" alt="Description">
<!-- With placeholder -->
<img
src="placeholder.jpg"
data-src="full-image.jpg"
loading="lazy"
alt="Description"
>
Intersection Observer API
// Modern approach using Intersection Observer
const images = document.querySelectorAll('img[data-src]');
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.removeAttribute('data-src');
observer.unobserve(img);
}
});
}, {
rootMargin: '50px 0px', // Start loading 50px before visible
threshold: 0.01
});
images.forEach(img => imageObserver.observe(img));
Component Lazy Loading (React)
import { lazy, Suspense } from 'react';
// Lazy load component
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</Suspense>
);
}
// Route-based lazy loading
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
JavaScript Module Loading
// Dynamic import
button.addEventListener('click', async () => {
const module = await import('./heavy-module.js');
module.doSomething();
});
// Conditional loading
if (userNeedsFeature) {
import('./feature.js').then(module => {
module.initialize();
});
}
Implementation Strategies
Progressive Image Loading
<!-- Low quality placeholder to high quality -->
<div class="progressive-image">
<img
class="placeholder"
src="tiny-blurred.jpg"
alt="Description"
>
<img
class="full"
data-src="full-resolution.jpg"
alt="Description"
>
</div>
<style>
.progressive-image {
position: relative;
}
.placeholder {
filter: blur(5px);
transition: opacity 0.3s;
}
.full {
position: absolute;
top: 0;
left: 0;
opacity: 0;
transition: opacity 0.3s;
}
.full.loaded {
opacity: 1;
}
</style>
Infinite Scroll with Lazy Loading
const content = document.querySelector('.content');
const loader = document.querySelector('.loader');
const loadMoreContent = async () => {
const response = await fetch(`/api/content?page=${nextPage}`);
const newContent = await response.json();
renderContent(newContent);
nextPage++;
};
// Observe loader element
const observer = new IntersectionObserver(entries => {
if (entries[0].isIntersecting) {
loadMoreContent();
}
});
observer.observe(loader);
Lazy Loading with Fallbacks
// Query DOM for images that need lazy loading
const images = document.querySelectorAll('img[data-src]');
// Check for native lazy loading support
if ('loading' in HTMLImageElement.prototype && images.length > 0) {
// Native lazy loading supported - convert NodeList to array for forEach
Array.from(images).forEach(img => {
img.loading = 'lazy';
});
} else if (images.length > 0) {
// Fallback to Intersection Observer or library
const script = document.createElement('script');
script.src = '/lazy-loading-polyfill.js';
document.body.appendChild(script);
}
Best Practices
Do’s ✅
- Set dimensions to prevent layout shift
<img
src="placeholder.jpg"
data-src="image.jpg"
width="800"
height="600"
loading="lazy"
>
- Use appropriate loading thresholds
// Load images just before they enter viewport
rootMargin: '100px 0px'
- Provide meaningful placeholders
- Blurred thumbnails
- Skeleton screens
- Solid colors
- SVG placeholders
- Prioritize above-the-fold content
<!-- Critical images load immediately -->
<img src="hero.jpg" loading="eager" fetchpriority="high">
<!-- Below-fold images lazy load -->
<img src="footer.jpg" loading="lazy">
Don’ts ❌
- Don’t lazy load critical content
- Hero images
- Logo
- Above-the-fold content
- LCP elements
- Don’t remove accessibility
<!-- Always include alt text -->
<img data-src="image.jpg" alt="Description" loading="lazy">
- Don’t lazy load tiny resources
- Small icons
- CSS files
- Critical JavaScript
Performance Metrics
Impact on Core Web Vitals
Largest Contentful Paint (LCP)
- Improves by reducing initial load
- Be careful not to lazy load LCP element
Interaction to Next Paint (INP)
- Better interactivity with less initial JavaScript
- Fewer resources competing for main thread
Cumulative Layout Shift (CLS)
- Can worsen if dimensions not specified
- Always reserve space for lazy loaded content
Common Libraries
Vanilla JavaScript
- lazysizes - High performance, no dependencies
- vanilla-lazyload - Lightweight, IntersectionObserver-based
- lozad.js - Simple, modern, lightweight
React
- react-lazyload - Component lazy loading
- react-intersection-observer - Hook-based
- react-lazy-load-image-component - Image specific
Vue
- vue-lazyload - Directive-based
- v-lazy-image - Simple image lazy loading
Accessibility Considerations
<!-- Provide context for screen readers -->
<img
class="progressive"
src="placeholder.jpg"
data-src="full-image.jpg"
alt="Product photo"
loading="lazy"
decoding="async"
/>
<script>
const img = document.querySelector('img.progressive');
const swapSrc = (el) => {
const full = el.getAttribute('data-src');
if (full) el.src = full;
};
if (img.complete) swapSrc(img);
else img.addEventListener('load', () => swapSrc(img), { once: true });
</script>
SEO Implications
Considerations
- Search engines now execute JavaScript
- Google supports lazy loading
- Ensure content is discoverable
- Use native
loading="lazy"
when possible
Implementation for SEO
<!-- Structured data for lazy loaded images -->
<script type="application/ld+json">
{
"@type": "ImageObject",
"contentUrl": "https://example.com/image.jpg",
"description": "Image description"
}
</script>
Testing Lazy Loading
Browser DevTools
- Network tab - Check when resources load
- Performance tab - Measure impact
- Coverage tab - See unused JavaScript/CSS
Tools
- Lighthouse - Performance audit
- WebPageTest - Waterfall analysis
- Chrome User Experience Report
Related Concepts
- Skeleton Screen - Shows while lazy loading
- Viewport - Triggers lazy loading
- DOM - Manipulated during lazy loading
- Progressive Enhancement - Core principle
Key Takeaways
- Lazy loading significantly improves initial page load
- Use native browser APIs when available
- Always specify dimensions to prevent layout shift
- Don’t lazy load critical above-the-fold content
- Provide meaningful placeholders during loading
- Test impact on Core Web Vitals
Stay updated with new patterns
Get notified when new UX patterns are added to the collection.
Last updated on