Lazy loading is a performance technique that delays loading non-critical resources, such as offscreen images, iframes, or JavaScript modules, until the moment they are actually needed instead of fetching everything upfront. By deferring work that the user may never trigger, the browser downloads and renders the visible part of a page faster, uses less bandwidth and memory, and improves key metrics like Largest Contentful Paint. It is one of the highest-impact, lowest-effort optimizations available on the modern web.
Key takeaways
- Lazy loading defers non-critical resources until they are needed, speeding up the initial render.
- The native
loading=lazyattribute handles images and iframes with no JavaScript. - Never lazy load above-the-fold content, or you delay what the user sees first.
- Reserve space with
width/heightoraspect-ratioto avoid layout shift. - For custom behavior, the Intersection Observer API is the efficient, modern approach.
- Code splitting applies the same idea to JavaScript bundles via dynamic imports.
How does lazy loading improve performance?
A typical page references far more bytes than the user needs to see the first screen. Loading all of it upfront competes for bandwidth, delays the content that matters, and wastes data on resources scrolled past or never reached.
Lazy loading changes the priority. The browser fetches what is visible or critical first, then loads the rest opportunistically or when the user approaches it. Concretely, this tends to:
- Reduce initial payload and the number of blocking requests.
- Improve Largest Contentful Paint by freeing bandwidth for the main content.
- Lower memory usage, especially on image-heavy or infinite-scroll pages.
- Save mobile data for content the user never views.
The effect is most dramatic on long, media-rich pages. An article with fifty images, an infinite-scroll feed, or a gallery would otherwise force the browser to fetch every asset before it settles down, even though the user may read only the first screen. Deferring the rest turns a heavy page into one that feels light and responsive from the first moment.
How do you lazy load images natively?
The simplest and most robust method needs no libraries. Add the loading attribute to an img or iframe.
<img src="photo.jpg" loading="lazy" width="800" height="600" alt="Product photo">
<iframe src="player.html" loading="lazy" width="560" height="315"></iframe>
The attribute accepts three values: lazy defers until the resource nears the viewport, eager loads immediately, and auto lets the browser decide. Always pair lazy images with explicit dimensions so the browser can reserve layout space before the file arrives.
When should you not lazy load?
Lazy loading the wrong things makes a page feel slower. The critical rule is to eagerly load anything visible during the initial render.
- Above-the-fold images. The hero image or LCP element must load eagerly; lazy loading it delays first paint and hurts your metrics.
- Tiny assets. The overhead of deferring a small icon can outweigh the savings.
- Content critical for SEO or accessibility that must be present regardless of scroll behavior.
How do you build custom lazy loading with Intersection Observer?
When you need control beyond native attributes, for example lazy loading CSS background images, video, or analytics widgets, use the Intersection Observer API. It reports when an element crosses a threshold relative to the viewport without expensive scroll listeners.
const images = document.querySelectorAll("img[data-src]");
const observer = new IntersectionObserver((entries, obs) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
obs.unobserve(img);
}
});
}, { rootMargin: "200px" });
images.forEach((img) => observer.observe(img));
The rootMargin option preloads slightly before the element is visible so content is ready by the time the user reaches it. Always call unobserve once loaded to free resources.
What is the difference between lazy loading and code splitting?
These terms overlap, so it helps to separate the concept from the specific technique.
| Technique | What it defers | Typical mechanism |
|---|---|---|
| Image lazy loading | Offscreen images and iframes | loading=lazy or Intersection Observer |
| Code splitting | JavaScript bundle chunks | Dynamic import() |
| Route-based lazy loading | Whole page components | Framework lazy route loaders |
Code splitting is simply lazy loading applied to your JavaScript. A dynamic import loads a module only when a feature is used:
button.addEventListener("click", async () => {
const { openEditor } = await import("./editor.js");
openEditor();
});
How do you avoid layout shift when lazy loading?
Cumulative Layout Shift happens when content jumps as deferred resources arrive. Because a lazy image has no intrinsic size until it loads, the browser cannot reserve space unless you tell it the dimensions.
- Set
widthandheightattributes on images so the browser computes the correct aspect ratio. - Alternatively, use the CSS
aspect-ratioproperty on the container. - Use a lightweight placeholder or blurred low-quality image so the space is visually filled while the full asset loads.
How does lazy loading affect Core Web Vitals?
Lazy loading interacts directly with Google’s Core Web Vitals, and the relationship can be positive or negative depending on what you defer. Understanding each metric prevents the classic mistake of optimizing one number while damaging another.
- Largest Contentful Paint (LCP) measures when the largest visible element renders. Lazy loading offscreen images frees bandwidth for the LCP element, which helps. But lazy loading the LCP element itself delays it, which hurts. Always load the hero eagerly and, where supported, hint it with high priority.
- Cumulative Layout Shift (CLS) measures unexpected movement. Deferred images that arrive without reserved space push content around, spiking CLS. Setting
widthandheightoraspect-ratiokeeps this at zero. - Interaction to Next Paint (INP) measures responsiveness. Deferring non-critical JavaScript through code splitting reduces the main-thread work competing with user input, which can improve it.
The practical rule that satisfies all three: eagerly load and size the content that is visible on first paint, defer everything else, and always reserve space for deferred media. A quick way to audit this is to throttle the network in your browser dev tools and watch whether the first screen paints promptly and stays still while lower content streams in.
How does the browser prioritize what to load?
Browsers assign an internal priority to every resource based on its type and position. Lazy loading cooperates with this system by lowering the effective priority of deferred assets so the browser spends early bandwidth on what matters. You can reinforce it: preload genuinely critical assets, mark the hero image as eager, and let the rest default to lazy. Fighting the browser’s heuristics, for example by eagerly loading dozens of below-the-fold images, simply recreates the problem lazy loading was meant to solve.
What are common lazy loading mistakes?
- Lazy loading the LCP image. The single most common regression; the hero must load eagerly.
- Hiding content from crawlers. Content that appears only after custom scroll handlers may not be indexed; keep important markup in the initial HTML.
- No fallback. If you rely purely on JavaScript, users with scripts disabled or errors see nothing; native attributes degrade gracefully.
- Forgetting dimensions. Skipping size hints reintroduces layout shift and undoes part of the benefit.
- Over-deferring. Deferring too aggressively causes blank areas and jank as the user scrolls; tune
rootMarginso content is ready in time.
Frequently asked questions
What is the simplest way to lazy load images?
Add the loading=lazy attribute to img and iframe elements. The browser then defers loading them until they are about to enter the viewport, with no JavaScript required.
Should I lazy load images above the fold?
No. Lazy loading content that is visible on first paint delays it and can hurt Largest Contentful Paint. Load above-the-fold images eagerly and lazy load only what is below.
Does lazy loading hurt SEO?
Not if done correctly. Modern crawlers render pages and support native lazy loading, but content that only loads on user scroll events may not be seen, so keep important content in the markup.
What is the difference between lazy loading and code splitting?
Lazy loading is the general idea of deferring a resource until needed. Code splitting is one application of it that breaks a JavaScript bundle into chunks loaded on demand.
Why do I get layout shift when lazy loading images?
Because the browser does not know the image size until it loads. Set width and height attributes or a CSS aspect ratio so space is reserved and the layout stays stable.
How does the Intersection Observer API help?
It efficiently notifies your code when an element enters or leaves the viewport, letting you trigger loading without costly scroll listeners. It is the standard way to build custom lazy loading.
Can I lazy load background images set in CSS?
Native loading=lazy only applies to img and iframe elements. For CSS background images you add a class with JavaScript via Intersection Observer once the element is near the viewport.