Tutorial

How to Lazy Load Images, Components, and Scripts

Lazy loading is the practice of deferring the download or execution of non-critical resources until they are actually needed. It is one of the most impactful performance strategies available: by loading only what the user needs right now, you reduce initial page weight, free up bandwidth for critical resources, and unblock the main thread for faster interactivity.

This guide covers lazy loading at three levels: images (the easiest win), components (for JavaScript-heavy applications), and third-party scripts (the most commonly overlooked). Each section includes code examples and common pitfalls.

LAZY LOAD (below fold) Below-fold images Heavy components (charts, maps) Third-party scripts (analytics) YouTube / social embeds Comment sections Chat widgets Cookie consent (after delay) NEVER LAZY LOAD LCP image (hero/banner) Critical CSS Above-fold text content Navigation / header Primary web fonts Core interactive elements First-party critical scripts

Step-by-step guide

1

Lazy load below-fold images with loading=lazy

Native lazy loading is the easiest performance optimization available. Add loading="lazy" to any <img> element that is below the initial viewport. The browser will defer the download until the user scrolls within approximately 1250px of the image (the exact threshold varies by browser and connection speed).

HTML -- Native image lazy loading

Hero


Feature 1

Feature 2

Feature 3

Tip: Add decoding="async" alongside loading="lazy". This tells the browser to decode the image off the main thread, reducing main thread blocking and improving INP.
2

Lazy load components with dynamic import()

For JavaScript-heavy pages, lazy loading entire components reduces the initial bundle size and speeds up hydration. Each framework has its own pattern, but all use import() under the hood. The bundler creates a separate chunk that is only downloaded when the component is rendered.

JavaScript -- Framework lazy loading patterns
// React
import { lazy, Suspense } from 'react';
const HeavyChart = lazy(() => import('./HeavyChart'));

function Dashboard() {
  return (
    Loading chart...
}> ); } // Vue 3 import { defineAsyncComponent } from 'vue'; const HeavyChart = defineAsyncComponent(() => import('./HeavyChart.vue') ); // Astro (zero JS by default, islands on demand) // Nuxt (auto lazy-loaded with Lazy prefix) // SvelteKit import { onMount } from 'svelte'; let ChartComponent; onMount(async () => { const mod = await import('$lib/HeavyChart.svelte'); ChartComponent = mod.default; });
Tip: Wrap lazy-loaded components in a container with min-height matching the expected component height. This prevents CLS when the component loads and expands.
3

Defer third-party scripts

Third-party scripts (analytics, ads, chat widgets, A/B testing) are the most common cause of main thread blocking. They compete for bandwidth and CPU with your own code. Move them out of the critical rendering path using these strategies (in order of preference):

HTML + JavaScript -- Script deferral strategies







Tip: Audit your third-party scripts with chrome://tracing or the Coverage tab in DevTools. Many sites load 500KB+ of unused third-party JavaScript on every page.
4

Lazy load iframes and embeds

Iframes are expensive -- a single YouTube embed loads ~600KB of JavaScript. Use the native loading="lazy" attribute on iframes, or implement a facade pattern that shows a lightweight placeholder until the user explicitly clicks to load the embed.

HTML -- Lazy iframe and facade pattern




Video thumbnail
5

Avoid lazy loading pitfalls

Lazy loading is not universally beneficial. Applying it to the wrong resources can actively hurt performance. Here are the most common mistakes:

Tip: Use the Chrome DevTools Coverage tab (Ctrl+Shift+P, then type Coverage) to identify which JavaScript is actually used during initial page load. Only code-split modules that have low initial coverage.

Frequently asked questions

Does lazy loading images hurt SEO?

No. Google's crawler processes native lazy loading (loading='lazy') correctly and indexes lazy-loaded images. Googlebot renders pages with JavaScript, so intersection observer-based lazy loading also works. However, always provide proper alt text and ensure images are in the HTML source (not injected purely via JavaScript) for best indexability.

Should I lazy load the LCP image?

Absolutely not. The LCP image should always use loading='eager' and fetchpriority='high'. Lazy loading the LCP image is one of the most common performance mistakes -- it delays the download until the image enters the viewport, adding hundreds of milliseconds to LCP. Only lazy load images that are below the initial viewport fold.

What is the difference between lazy loading and code splitting?

Lazy loading is the general strategy of deferring non-critical resources. Code splitting is a specific build-time technique that divides JavaScript bundles into smaller chunks. Code splitting enables lazy loading of components -- the dynamic import() statement triggers the download of a separate chunk when the component is needed.

How do I lazy load YouTube videos without iframe performance cost?

Use a facade pattern: display a static thumbnail image with a play button overlay. When the user clicks, replace the thumbnail with the actual YouTube iframe. This avoids loading the ~600KB YouTube embed until the user explicitly requests it. Libraries like lite-youtube-embed implement this pattern.

Can lazy loading hurt Cumulative Layout Shift?

Yes, if implemented incorrectly. When a lazy-loaded image or component loads and expands, it pushes surrounding content down. Always specify width, height, or aspect-ratio on lazy-loaded elements to reserve space before they load. For lazy-loaded components, use a skeleton placeholder with matching dimensions.

Sarah Kim

Frontend Developer at WebVitals.tools

Sarah focuses on image optimization and responsive design. She maintains performance optimization guides for Next.js, Astro, and SvelteKit.