What lazy loading actually is
Lazy loading is the practice of delaying the download of a resource until the moment it is actually needed, rather than fetching everything the instant a page starts to open. The opposite approach, loading it all up front, is called eager loading, and for years it was simply how the web worked. Every image, every embedded video, every script came down together, whether the visitor ever scrolled far enough to see it or not.
Think about a long article with twenty photos spread through it. A visitor who reads the first two paragraphs and leaves has seen maybe one image. With eager loading, their browser still downloaded all twenty, along with the data cost and the time that took. Lazy loading fixes that mismatch. The first image or two load right away because they are on screen. The other eighteen wait quietly until the reader scrolls toward them, and if the reader never does, those images are never fetched at all.
The name is a little unfair, because there is nothing careless about it. A better way to picture it is a waiter who brings your first course promptly and holds the rest in the kitchen until you are ready, instead of crowding the whole meal onto the table at once and letting most of it go cold. The work still happens. It just happens at the right time, in the right order, for the person actually sitting there.
Lazy loading applies to more than images. You can defer video, embedded maps and social widgets inside iframes, and even chunks of your own JavaScript so that code for a feature loads only when someone uses that feature. The principle is identical in every case: do not pay for something before you need it. Once that idea clicks, you start seeing opportunities to apply it all over a typical site.
How lazy loading works
To understand how lazy loading works, it helps to picture the browser window as a frame. Everything inside that frame is the viewport, the part of the page a visitor can see without scrolling. Content above and to the sides that fits on the first screen is often called above the fold. Everything below, which you reach by scrolling, is below the fold. Lazy loading is really a rule about that boundary: load what is inside or near the viewport now, and defer what is far below it.
The browser needs a way to know when a deferred resource is getting close to the viewport so it can start fetching in time. There are two common ways this happens. The first is built into the browser itself. When you mark an image or an iframe as lazy, the browser watches the scroll position and begins loading the resource a little before it enters view, so it is usually ready by the time the visitor reaches it. You write one attribute and the browser handles the timing.
The second way is a browser feature called the Intersection Observer, which lets your own code watch elements and react when they cross into or out of the viewport. This is the engine behind most custom lazy loading and behind the tools that older browsers relied on before the native attribute existed. You tell it which elements to watch and how close to the edge to trigger, and it calls your function at the right moment so you can swap in the real content.
Either way, the sequence is the same. When the page first loads, the browser skips the deferred resources and reserves space for them. As the visitor scrolls and a deferred element approaches the viewport, the browser or your code kicks off the real download. By the time the element is on screen, the content is usually there. The visitor rarely notices any of this happening, which is exactly the point. Good lazy loading is invisible.
One detail worth holding onto for later: reserving space matters. If the browser does not know how tall an image will be until it arrives, the page can jump as each deferred image pops in and pushes everything down. That jump is called layout shift, and it is the single most common way lazy loading goes wrong. We will come back to how to prevent it, because it is easy to avoid once you know the cause.
Why lazy loading matters
Lazy loading is not a nice extra. For image heavy and media heavy sites it is often the biggest single improvement available, and it pays off in three ways at once.
Faster initial load
The first benefit is speed where it counts, at the start. A browser can only download so many things at once, so every below the fold image competing for bandwidth on load is stealing attention from the content the visitor is waiting to see. Defer the offscreen resources and the browser spends its early moments on what matters, so the first screen paints sooner. A visitor decides in the first couple of seconds whether a site feels fast or slow, and that judgment happens before they scroll at all.
Less bandwidth and data
The second benefit is that you stop downloading things nobody looks at. On a long page, most visitors never reach the bottom. Every image and embed below their stopping point is data that was fetched for nothing, and on a phone with a limited plan that waste is real money and battery for your visitor. Lazy loading means a person only pays for the content they actually reach. For a media rich site that difference in transferred bytes can be large.
Better Core Web Vitals
The third benefit is that lazy loading, done well, helps the scores Google uses to measure real world experience, known as Core Web Vitals. Loading fewer resources up front frees the network and the main thread to render the first screen quickly, which supports the loading and responsiveness measurements. The catch is the word "well." The same technique done carelessly can hurt the visual stability score, so it is a tool you have to aim correctly. We cover exactly how in the pitfalls section, and our deeper explainer on what Core Web Vitals are walks through each metric in detail.
Put the three together and the case is simple. Faster first paint keeps visitors from bouncing, lower data use respects people on phones, and stronger vitals help you rank. That is a rare change that helps users, your hosting bill, and search all at the same time. It also pairs naturally with other speed work, which is why we treat it as a standard step rather than an optional one. If you want the full picture, our guide on how to improve website speed puts lazy loading in context with the other moves that matter.
Lazy loading vs eager loading
It would be a mistake to read all this and conclude that everything should be lazy. Eager loading is not a flaw to be stamped out. It is the correct choice for anything the visitor needs immediately, and getting this balance right is most of the skill involved.
Eager loading means fetching a resource as soon as possible, on page load, with no delay. That is exactly what you want for the content of the first screen: the logo, the main heading area, and above all the largest image a visitor sees when the page opens, often the hero banner or a lead product photo. These should never be lazy loaded, because delaying them means delaying the very thing the person came to see. Marking them eager, or simply not deferring them, tells the browser to treat them as the priority they are.
Lazy loading is for everything the visitor does not need yet: images further down the article, the map in the footer, the comments section, the video embedded three screens below, the code for a feature they may never open. The rule of thumb is a question you can ask about any resource: will the visitor see or use this in the first second? If yes, load it eagerly. If no, defer it.
| Situation | Load it eagerly | Load it lazily |
|---|---|---|
| Hero or main banner image | Yes, always | No, never |
| Logo and above the fold icons | Yes | No |
| First screen product photo | Yes | No |
| Images further down the page | No | Yes |
| Footer map or embedded widget | No | Yes |
| Video the visitor scrolls to | No | Yes |
| Code for a modal or later route | No | Yes |
| Comments and related posts | No | Yes |
There is a subtle trap in the middle. Some sites lazy load absolutely everything, including the hero image, because a plugin turned it on across the board. That single mistake can make a page feel slower than doing nothing, because the browser now waits to fetch the most important image instead of rushing it. The fold is the dividing line. Above it, be eager. Below it, be lazy. Treat that as the default and you will get most of the benefit with none of the harm.
Lazy loading images
Images are where lazy loading gives back the most, because they are usually the heaviest thing on a page, and where the technique has become easiest to apply. For most sites you no longer need a script or a library at all. Browsers understand a single attribute.
The native loading attribute
Adding loading="lazy" to an image tag tells the browser to defer that image until it nears the viewport. It is one word and it works in every current major browser.
<img src="/photos/desk.jpg" alt="A tidy desk" loading="lazy" width="800" height="600">
The counterpart is loading="eager", which forces immediate loading and is what you want on your hero image, though eager is also the default when you leave the attribute off. So the practical pattern is: leave your above the fold images alone or mark them eager, and add loading="lazy" to the images below the fold.
Always set width and height
Notice the width and height on that tag. Those two numbers are not optional decoration. They let the browser reserve the correct amount of space for the image before it arrives, so the page does not jump when the deferred image finally loads. Skip them and you invite the layout shift problem we keep mentioning. Set them, and the browser draws an empty box of the right size, then fills it in place with no jolt. You can still size the image with CSS for responsiveness, but the attributes give the browser the aspect ratio it needs to hold the space.
Serve the right size with srcset
Lazy loading pairs naturally with responsive images. The srcset attribute lets you offer several versions of an image at different widths and let the browser pick the one that fits the visitor's screen, so a phone does not download a version sized for a large monitor. Combine it with lazy loading and you get two savings at once: fewer images fetched, and each one no bigger than it needs to be.
<img
src="/photos/desk-800.jpg"
srcset="/photos/desk-480.jpg 480w, /photos/desk-800.jpg 800w, /photos/desk-1200.jpg 1200w"
sizes="(max-width: 600px) 480px, 800px"
alt="A tidy desk"
loading="lazy"
width="800" height="600">
A placeholder while it loads
Some sites show a low quality blurred version or a soft background color in the reserved space while the full image loads, so the box is never blank. This is optional polish. It can make the experience feel smoother, especially on slower connections, but it is not required for lazy loading to work. If you use one, keep the placeholder tiny so it does not undo the savings you just made.
For most business sites, the native attribute plus width, height, and a sensible srcset is the entire recipe. It is a small change with a large payoff, and you can read the fuller reference for images on web.dev if you want the deeper technical notes.
Lazy loading video and iframes
Images are the obvious target, but embedded content can be even heavier, and it is often forgotten. An embedded video player, a map, or a social feed lives inside an iframe, which is a small web page loaded inside yours. Each of those can pull in a surprising amount of code and data of its own, and if it sits in your footer, every visitor pays for it on load even though almost nobody scrolls that far.
Iframes with the native attribute
The good news is that iframes accept the same attribute as images. Add loading="lazy" and the browser defers the whole embed until it nears the viewport.
<iframe src="https://www.example-map.com/embed?place=shop"
width="600" height="400" loading="lazy"
title="Map to our shop"></iframe>
That one change can make a real difference for a map or a comments widget parked at the bottom of the page, because you are no longer loading an entire second web page that the visitor may never see.
Video worth special care
Video deserves extra thought because players are among the heaviest embeds on the web. A common and effective pattern is the facade: instead of embedding the full player on load, you show a lightweight thumbnail image with a play button drawn on top. Only when the visitor clicks does the real player load and start. To the visitor it looks like a normal video, but the heavy player code never downloads unless someone actually wants to watch. For a page with several videos, this can cut the initial weight dramatically.
For a native HTML video element that you do want present, the preload="none" attribute tells the browser not to download the video data until playback begins, which is the video equivalent of deferring the load. Pair that with a poster image so the visitor still sees a still frame in the meantime.
<video controls preload="none" poster="/video/cover.jpg" width="640" height="360">
<source src="/video/tour.mp4" type="video/mp4">
</video>
The theme across images, iframes, and video is the same one from the start of this guide. If the visitor is not looking at it yet, do not spend their bandwidth on it yet. Embeds just tend to be the most expensive place that rule pays off, so they are worth a careful pass.
Lazy loading JavaScript and components
So far we have deferred media, but the same idea applies to your own code, and on interactive sites this is often where the largest wins hide. A modern web application can ship a lot of JavaScript, and much of it is for features a given visitor never touches: a checkout flow, an admin panel, a chart library, a rich text editor, a photo gallery. Sending all of that on the first load makes the initial download bigger and slower for everyone, even the person who only wanted to read the home page.
Code splitting
The technique that fixes this is called code splitting. Instead of bundling your entire application into one large file, you break it into smaller pieces that load on demand. The code for the checkout stays in its own piece and only downloads when someone starts to check out. The code for the photo gallery loads only when the gallery opens. The visitor downloads the core of the site quickly and picks up the rest as they go, if they go there at all. It is lazy loading applied to logic rather than pictures.
Dynamic import
The building block for this is the dynamic import, a way to load a module at the moment you need it rather than at the top of the file. A normal import pulls the code in up front. A dynamic import returns the code later, on request, which lets you tie the download to an action like a click.
// loads the heavy module only when the button is clicked
button.addEventListener('click', async () => {
const { openEditor } = await import('./editor.js');
openEditor();
});
Route based splitting
The most common and highest value place to split is by route, meaning by page. A visitor on your home page does not need the code for your pricing page, your blog, or your account settings yet. Route based splitting loads the code for each page only when the visitor navigates to it. The first page arrives lean, and each later page brings its own code as it is opened. For a multi page application this alone can shrink the initial download substantially, which is why frameworks make it a default rather than an afterthought.
You do not usually wire up dynamic imports and route splitting by hand across a whole site. The build tools and frameworks below handle the mechanics once you tell them where the split points are.
Lazy loading in React
React has this pattern built in, which is why lazy loading in React comes up so often. The two pieces you need are React.lazy and Suspense. Together they let you load a component only when it is first rendered, and show a fallback in the meantime.
React.lazy takes a function that dynamically imports a component. React does not download that component's code until the component is actually rendered for the first time. Suspense wraps the lazy component and provides a fallback, usually a spinner or a placeholder, to show while the code is being fetched.
import { lazy, Suspense } from 'react';
const Gallery = lazy(() => import('./Gallery'));
function Page() {
return (
<Suspense fallback={<p>Loading gallery...</p>}>
<Gallery />
</Suspense>
);
}
The most common use is route based splitting, exactly the pattern from the last section. You wrap each route's component in React.lazy so that visiting a page loads that page's code and nothing else. The home page no longer carries the weight of the settings screen or the dashboard.
const Home = lazy(() => import('./routes/Home'));
const Pricing = lazy(() => import('./routes/Pricing'));
const Account = lazy(() => import('./routes/Account'));
// each route now loads its own code on demand,
// wrapped in a single Suspense boundary with a fallback
The other strong use is deferring a heavy component that sits below the fold or behind an interaction. A charting library, a map, a rich editor, or a modal that only opens on a click are all good candidates. Load them lazily and the visitor who never opens them never pays for them. The official documentation on react.dev covers the details and the edge cases, and the same concepts carry over to other frameworks, which offer their own equivalents of the lazy component and the loading fallback.
One caution specific to component lazy loading: always provide a sensible fallback and, where you can, reserve space for the component so its arrival does not shove the page around. The layout shift concern from images applies just as much to a chunk of interface that pops into place.
How to implement lazy loading
There are three broad ways to add lazy loading, and most sites end up using a mix. The right choice depends on what you are deferring and what you are building on.
The native attribute
For images and iframes, start with the native loading="lazy" attribute. It is the simplest option, it needs no code or library, and it is supported across current browsers. For the large majority of content sites this is all you need for media. Add the attribute to below the fold images and iframes, keep width and height on your images, and you are done.
The Intersection Observer
When you need more control than the attribute gives you, reach for the Intersection Observer. It lets you watch any element and run your own code when it approaches the viewport, which is useful for custom effects, for lazy loading background images that CSS applies, for triggering animations, or for loading a section of content on demand. It is also how you would support very old browsers that predate the native attribute, though that need is rare now.
const io = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src; // swap in the real image
io.unobserve(img); // stop watching once loaded
}
});
}, { rootMargin: '200px' }); // start a little before it enters view
document.querySelectorAll('img[data-src]').forEach((img) => io.observe(img));
The rootMargin in that example is a small but important touch. It tells the observer to trigger a bit before the element actually reaches the viewport, so the resource has a head start and is usually ready by the time the visitor sees it. You can read the full reference for the Intersection Observer on developer.mozilla.org.
Framework and build tools
For JavaScript and components, lean on your framework. React gives you React.lazy and Suspense, other frameworks offer their own lazy loading helpers, and modern build tools split your code into on demand chunks once you mark the split points. You rarely hand roll this. You tell the tool where to divide and it produces the separate pieces and loads them when needed.
| Approach | Best for | Effort | Control |
|---|---|---|---|
| Native loading attribute | Images and iframes | Very low, one attribute | Basic, browser decides timing |
| Intersection Observer | Custom cases, effects, backgrounds | Moderate, some code | High, you set the rules |
| Framework tools | Components and route code | Low to moderate | High, tuned to your app |
A sensible plan for most sites: use the native attribute for images and iframes, use your framework's tools to split code by route and defer heavy components, and reach for the Intersection Observer only when you have a specific need the first two do not cover. That combination gives you nearly all the benefit for very little effort.
Lazy loading and SEO
Business owners are right to ask whether lazy loading can hurt search rankings, because a technique that hides content from visitors could in theory hide it from search engines too. The short answer is that done correctly, lazy loading is safe for SEO and can help it through better speed. Done carelessly, it can cause a crawler to miss content. The difference comes down to a few habits.
Search engine crawlers do not scroll a page the way a person does. They render the page and look at what is there. The native loading="lazy" attribute is designed with this in mind, and major search engines handle it correctly, loading lazy images so they can index them. Where sites get into trouble is with custom scripts that only reveal content on a real scroll or a real click that a crawler never performs. If your important text or images only appear after an interaction the crawler will not take, that content may go unseen.
The practical rules are straightforward. Prefer the native attribute, which crawlers understand, over custom scroll scripts for anything that matters for search. Never lazy load text content itself. Lazy loading is for media and code, not for the words on the page, so your headings, paragraphs, and links should always be in the initial HTML. Make sure lazy loaded images still carry proper alt text and, where relevant, appear in your image sitemap, so search engines can find and understand them. And test with the tools below to confirm that a crawler sees the same content a visitor does.
There is also a positive side. Because lazy loading improves loading speed and helps Core Web Vitals, and because those signals feed into how search engines rank pages, doing it well can lift your visibility rather than threaten it. The goal is simply to make sure crawlers see everything that counts, which they will if you keep text eager, use the native attribute for media, and avoid hiding anything important behind an interaction. If speed and search are your main concern, it is worth reading our companion piece on what Core Web Vitals are alongside this one.
Pitfalls to avoid
Lazy loading is one of those techniques that is easy to start and easy to get slightly wrong. Here are the mistakes we see most, and how to sidestep each.
Do not lazy load the hero or LCP image
This is the big one. Every page has a largest element that paints when it opens, often the hero image or a lead photo, and search engines measure how quickly it appears. If you lazy load that image, you are deliberately delaying the exact thing being measured, which makes the page feel slower and the score worse. Keep the main above the fold image eager. Lazy loading is for what comes after it, never for the first thing the visitor sees.
Missing dimensions cause layout shift
When a deferred image loads without reserved space, everything below it jumps down as it appears. That visual jolt is layout shift, and it both annoys visitors and hurts the visual stability score. The fix is simple and we have mentioned it throughout: set width and height on images, or a fixed aspect ratio in CSS, so the browser holds the right space before the image arrives. Do the same for lazy loaded components by reserving room for them.
Blank space and clumsy placeholders
If a placeholder is missing or poorly handled, visitors see empty gaps or flashes as content pops in. A soft background color or a small blurred preview in the reserved space keeps the page looking intentional while the real content loads. Keep any placeholder tiny so it does not eat the savings.
No fallback when scripts fail
If your lazy loading depends on JavaScript and that script fails to run, on a flaky connection or in an unusual browser, the deferred content may never appear at all. The native attribute avoids this because the browser handles it without your code. If you build a custom solution, make sure there is a path to the real content when scripts do not run, so a failure degrades to a slower page rather than a broken one.
Over lazy loading
More lazy loading is not automatically better. If you defer images that are already in the viewport, or split code so aggressively that the visitor waits on a tiny download for every small action, you add delay and complexity for no gain. Lazy load what is genuinely offscreen or genuinely optional, and leave the near term content eager. The aim is the right thing at the right time, not the most deferral possible.
Every one of these comes down to aim. Lazy loading is a precise tool, not a switch you flip site wide. Point it at the offscreen and optional, protect against layout shift and script failure, and keep the important first screen eager. Get that right and the pitfalls simply do not appear.
Best practices
Pulling the guidance together, here is a checklist you can apply to almost any site. None of it is complicated. It is mostly about being deliberate rather than flipping everything on at once.
- Keep the first screen eager. Your hero image, logo, and anything above the fold should load immediately.
- Lazy load below the fold media. Add
loading="lazy"to images and iframes the visitor has to scroll to reach. - Always set width and height on images, or a fixed aspect ratio, so deferred content never shifts the layout.
- Use a facade for heavy video embeds, showing a thumbnail and loading the real player only on click.
- Split your JavaScript by route so each page loads only its own code, and defer heavy optional components.
- Prefer the native attribute over custom scroll scripts, especially for anything that matters for search.
- Never lazy load text. Words, headings, and links belong in the initial HTML so crawlers and readers get them right away.
- Give lazy content a graceful fallback and a sensible placeholder, so a slow connection or failed script does not break the page.
- Measure before and after, so you keep what helps and undo what does not.
You do not have to do all of this at once. Start with images, since they usually give the fastest return, then move to iframes and video, and take on code splitting when you are working on an interactive site where it pays off. Each step stands on its own. In our experience the images pass alone is often enough to move a page's speed noticeably, and the rest is worth doing as the opportunity comes up. If you want the wider set of speed moves that pair with this, our guide on how to improve website speed lays them out, and our explainer on what a CDN is covers another change that works alongside lazy loading to cut load times.
Measuring the impact
Any performance change is worth measuring, because it is easy to assume something helped when it did nothing, or worse, quietly made things slower. The good news is that the tools you need are free and built into the browser.
The most useful is Lighthouse, which is included in the developer tools of Chrome and similar browsers. It runs a full audit of a page and reports on loading speed, the Core Web Vitals, and a list of specific opportunities. Two of its suggestions relate directly to lazy loading: it will flag when you are shipping images that are offscreen but loaded up front, often phrased as an offscreen images opportunity, and it will point out the largest element it timed, which tells you whether you accidentally lazy loaded something you should not have. Run it before your changes and after, on the same page, and compare.
The routine we suggest is simple. Pick a representative page, ideally one that is media heavy or code heavy. Record the current numbers: how large the initial download is, how soon the first screen paints, and the largest element timing. Apply lazy loading to the offscreen media and, where relevant, split the code. Run the audit again. You are looking for a smaller initial download and a first screen that appears at least as fast, with no worsening of the layout stability score. If the largest element timing got slower, check that you did not defer the hero image, which is the usual cause.
It also helps to watch real visitors over time, not just lab tests. The field data in tools that report Core Web Vitals from actual users will show whether the improvement holds up across the range of devices and connections your audience really uses, which a single test on a fast machine can miss. A change that looks great in the lab but relies on a fast connection may help less in the field, and that is worth knowing.
The point of measuring is not paperwork. It is to keep the changes that help and reverse the ones that do not, with evidence rather than guesswork. Lazy loading is usually a clear win on media heavy pages, but the only way to be sure for your site is to look. If you would rather not run these audits yourself, that is exactly the kind of work we do, and you can see the range of it across our services.
Final thoughts
So, what is lazy loading in one honest sentence? It is the simple discipline of not downloading anything until the visitor actually needs it, and it is one of the most reliable ways to make a site faster, lighter, and friendlier to the people on slow phones who make up much of any real audience. Load the first screen eagerly, defer the media and code below it, and you give visitors a page that feels quick from the first moment.
The technique has never been easier to adopt. For images and iframes, a single attribute does most of the work. For video, a thumbnail facade keeps heavy players out of the initial load. For interactive sites, splitting code by route and deferring heavy components with tools like React.lazy can shrink the first download in a way visitors feel immediately. The whole thing rests on one idea applied consistently, which is what makes it approachable even if you are not a developer.
The only real risk is aiming it wrong: deferring the hero image, forgetting image dimensions and inviting layout shift, or hiding text and content behind a scroll that crawlers never perform. Avoid those, measure before and after with Lighthouse, and lazy loading becomes a change that helps your visitors, your data bill, and your search visibility at the same time. If you would like help applying it to your site the right way, without the jumps and without hurting your rankings, we are glad to take a look. You can request a free quote whenever you are ready and we will give you a straight read on what will move the needle.