
Lazy loading videos for ecom
If large images can impact a page load, think about video. It’s 30 images per second.
Sometimes your product needs to be seen in action, and images won’t cut it. For those circumstances, you need video above the fold, and maybe mulitple videos per page.
Don’t worry - we’re going to show you how to load HD video without impacting website performance.
This tutorial is specific for Shopify, but the principles apply to any web technology.
What Actually Happens (The Problem)
Most ecom sites treat videos the same way they treat images. Load everything upfront. All at once. Every video on the page, whether someone scrolls to it or not, starts downloading immediately.
Think about the math for a second. A 30-second product video at HD quality? That's roughly 30 frames per second times 30 seconds. You're asking the browser to process 900 images worth of data before someone even scrolls down the page.
Now multiply that by 5-7 videos on a typical product landing page.
Yeah. That's the problem.
Enter: The Video Facade + Lazy Loading Combo
We borrowed a concept from SaaS projects.
Instead of loading the actual video file, you load a still image first. This image is the first frame of your video, essentially a poster. It appears instantly, takes up minimal bandwidth, and holds the space while the actual video streams in the background.
This is called a facade. It’s fake video. It loads in half the time, and takes the same space as the video.
Then, once the facade is on screen - we download the actual video. If your page has multiple videos, the user won’t see all of them at once. We shouldn’t download them all at the same time.
The average landing page scroll rate is 43%. Meaning most users will never even find the last videos.
Buckle up - it’s about to get technical.
How It Works (The Technical Side)
We will make a script (load_scheduler.js) which does three things:
- Identifies videos on page load — It scans the DOM for any video element with a
data-srcattribute instead ofsrc. These are your "inactive" videos, waiting in the wings. - Waits for them to appear — Using the Intersection Observer API, the script watches these videos. The moment one enters the viewport (becomes visible), it triggers.
- Switches them on — When a video comes into view, the script swaps
data-srctosrc. Now the browser says "oh, there's a real video here" and starts loading it.
The poster image (pulled from Shopify's API) was already sitting there as a placeholder. Now the video streams in behind it.
Implementation: Step by Step
Step 1: Update Your Video Tags
Instead of this:
html
<video controls>
<source src="https://cdn.shopify.com/video.mp4" type="video/mp4">
</video>
Do this:
html
<video controls poster="https://cdn.shopify.com/images/first-frame.jpg">
<source data-src="https://cdn.shopify.com/video.mp4" type="video/mp4">
</video>
The poster attribute is your placeholder image (Shopify can serve this from your CDN). The data-src keeps the video URL hidden until it's needed.
Step 2: Add the Load Scheduler Script (Shopify)
You have two options:
Option A: Quick Copy-Paste (Recommended)
Open your theme.liquid file (Admin → Themes → Edit code → Find theme.liquid). Paste this before the closing </body> tag:
liquid
<script>
document.addEventListener('DOMContentLoaded', function() {
// Select all video source elements with data-src
const videoSources = document.querySelectorAll('source[data-src]');
if ('IntersectionObserver' in window) {
const videoObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const source = entry.target;
source.setAttribute('src', source.getAttribute('data-src'));
source.removeAttribute('data-src');
// Reload the video element
source.closest('video').load();
observer.unobserve(source);
}
});
}, {
rootMargin: '50px'
// Start loading 50px before video enters viewport
});
videoSources.forEach(source => videoObserver.observe(source));
} else {
// Fallback for browsers without IntersectionObserver
videoSources.forEach(source => {
source.setAttribute('src', source.getAttribute('data-src'));
source.removeAttribute('data-src');
});
}
});
</script>
(Hint: add this at the bottom of your <body> tag, after everything else. It will result in better performance)
Option B: Create a Separate Asset File
If you prefer keeping things organized:
- Go to Admin → Themes → Edit code
- Click "Add a new file" in the left sidebar
- Name it
load-scheduler.js(it auto-creates in the assets folder) - Paste the JavaScript code (without the
<script>tags) - Go to
theme.liquidand add this before the closing</body>tag:
liquid
{{ 'load-scheduler.js' | asset_url | script_tag }}
Either way works. Option A is faster, Option B is cleaner if you're managing multiple custom scripts.
Step 3: Get the Poster Image from Shopify
If you're pulling product videos from Shopify, you can grab the first frame using their API:
liquid
{% assign video = product.featured_media %}
{% if video.media_type == 'video' %}
<video controls poster="{{ video.preview_image.src | img_url: '800x' }}">
<source data-src="{{ video.sources[0].url }}" type="video/mp4">
</video>
{% endif %}
The preview_image.src gives you that first frame. Perfect for your poster.
Step 4: Test in DevTools
Open your browser's Network tab. Reload the page. You should see:
- All poster images load immediately
- Video files do NOT load until you scroll to them
- Once in view, videos start downloading and play seamlessly
That's it. That's the win.
Testing & Validation
Network Inspection
Before and after comparison is your best friend here. Use Chrome DevTools:
- Open Network tab
- Filter by "media" to see video requests
- Reload your page and note total video requests
- Scroll through the page normally
- See when videos actually load (should be right when they enter viewport)
You should notice fewer initial requests and more staggered loading as users scroll.
Performance Metrics
Check your Lighthouse scores (Devtools → Lighthouse). Run reports before and after implementation. Watch for improvements in:
- Largest Contentful Paint (LCP)
- Cumulative Layout Shift (CLS)
Real User Monitoring
Most importantly, track your actual metrics. Page load time, bounce rate, time on page, conversion rate. The script should show measurable impact within a week or two.
With our clients, we typically see 15-40% improvement in initial load time on video-heavy pages, depending on how many videos are below the fold.
Conclusion
You're not removing the premium video experience. You're delivering it smarter.
Users still get their HD product videos. The page still feels rich and engaging. But now it loads in a fraction of the time because you're not forcing their browser to download every video before they've even scrolled.
It's the performance equivalent of not printing the entire menu when someone walks into a restaurant. You give them the appetizer section first.
The result? Faster perceived load times, less bandwidth waste, and typically a noticeable bump in how far users scroll and how long they stay. That compounds into better conversion metrics downstream.
If you implement this, let me know how it goes. This approach works across most ecom platforms—Shopify, WooCommerce, custom builds. The principle is the same: load what's visible, defer what's not.