Complete HTML Course Module 12: Advanced: Modern HTML Features and Performance

Lazy Loading, preload and Script Loading (defer and async)

  • Beginner
  • Lesson 12.2
  • 5 min read
  • 52 of 69 in the course

In short

The way you load images, fonts and scripts has a big effect on page speed. In this lesson you will learn lazy loading, preload, preconnect, and how defer, async and type="module" change script loading. What is lazy loading? Lazy loading means "only download it when the user is about to see it".

The way you load images, fonts and scripts has a big effect on page speed. In this lesson you will learn lazy loading, preload, preconnect, and how defer, async and type="module" change script loading.

What is lazy loading?

Lazy loading means "only download it when the user is about to see it". Imagine a long blog post with 30 photos. Most visitors only read the top part. Without lazy loading, the browser downloads all 30 photos at the start. With lazy loading, it downloads photos as the user scrolls near them.

In HTML, lazy loading is one attribute: loading="lazy". It works on <img> and <iframe> in all modern browsers.

<!-- Hero image at the top: load right away -->
<img src="/images/hero.webp" alt="Fresh vegetables on a market stall" width="1600" height="900" fetchpriority="high">

<!-- Images further down: load when needed -->
<img src="/images/carrots.webp" alt="A bunch of orange carrots" width="800" height="600" loading="lazy" decoding="async">
<img src="/images/tomatoes.webp" alt="Red tomatoes in a basket" width="800" height="600" loading="lazy" decoding="async">

<!-- A YouTube video near the bottom of the page -->
<iframe src="https://www.youtube-nocookie.com/embed/VIDEO_ID" title="How we grow our tomatoes" width="560" height="315" loading="lazy" allowfullscreen></iframe>

The hero image loads at once with high priority. The carrot and tomato pictures and the video wait until the user scrolls close to them. An embedded video is heavy (it loads a whole player), so lazy loading an <iframe> can save a lot.

The decoding="async" attribute lets the browser turn the downloaded file into pixels (called decoding) without pausing other work. It is a small extra hint, safe to add to images below the top of the page.

Common mistake: Lazy loading images at the top of the page, especially the hero image. These should load immediately. Only lazy load images that are below the first screen.

preload and preconnect: telling the browser early

The browser discovers files as it reads your HTML and CSS. Some important files are found late, for example a font that is only mentioned deep inside a CSS file. Resource hints are <link> tags in the <head> that tell the browser about important things early.

  • rel="preconnect" opens the connection to another server early. Like calling a restaurant to book a table before you leave home.
  • rel="preload" downloads a specific file early, because you know the page will need it soon.
<head>
  <meta charset="utf-8">
  <title>Market Fresh – Organic Vegetables</title>

  <!-- Connect early to the server that hosts our fonts -->
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

  <!-- Download the main font early -->
  <link rel="preload" href="/fonts/inter-var.woff2" as="font" type="font/woff2" crossorigin>

  <!-- Download the hero image early (it is set as a CSS background) -->
  <link rel="preload" href="/images/hero.webp" as="image" fetchpriority="high">

  <link rel="stylesheet" href="/css/style.css">
</head>

The as attribute tells the browser what kind of file it is, so it gives it the right priority. Fonts need crossorigin, even on your own site, or the browser will download them twice.

Tip: Only preload two or three truly important files. Preloading too much makes everything compete, and the page can get slower instead of faster.

Script loading: defer vs async vs type="module"

A normal <script src="..."> in the <head> blocks the page: the browser stops reading the HTML, downloads the script, runs it, and only then continues. Users stare at a blank screen. The defer and async attributes fix this.

Script tagDownloadsRunsOrder kept?Good for
<script src>Stops HTMLAt onceYesAlmost never in <head>
<script defer>In backgroundAfter the HTML is fully readYesYour own site scripts
<script async>In backgroundAs soon as it is downloadedNoIndependent scripts like analytics
<script type="module">In backgroundLike defer by defaultYesModern JavaScript with import
<head>
  <!-- Your main scripts: run in order after the HTML is ready -->
  <script src="/js/menu.js" defer></script>
  <script src="/js/cart.js" defer></script>

  <!-- Analytics does not depend on anything: run whenever ready -->
  <script src="https://analytics.example/track.js" async></script>

  <!-- Modern JavaScript module: deferred automatically -->
  <script type="module" src="/js/app.js"></script>
</head>

With these attributes the page shows content quickly. menu.js always runs before cart.js, and both can safely use any element in the page. The analytics script runs whenever it arrives. The module needs no defer, because modules are deferred by default.

Core Web Vitals in plain words

Core Web Vitals are three measurements Google uses to judge how a page feels to real users. They are also a small part of how Google ranks pages.

  • LCP (Largest Contentful Paint): how fast the biggest thing on screen, usually the hero image or main heading, appears. Good: 2.5 seconds or less. Helped by fetchpriority="high", preload and no lazy loading on the hero.
  • INP (Interaction to Next Paint): how fast the page reacts when you click or type. Good: 200 milliseconds or less. Helped by less JavaScript and using defer.
  • CLS (Cumulative Layout Shift): how much the page jumps around while loading. Good: 0.1 or less. Helped by width and height on images and iframes.

You can measure them with PageSpeed Insights or the Lighthouse panel in Chrome's developer tools.

Practice exercise

Build a long page with a hero image and ten more images. Add fetchpriority="high" to the hero, and loading="lazy" with width and height to the others. Add two scripts with defer. Open the Network tab in developer tools, reload, and scroll slowly to watch the images load.

Hint: Tick "Disable cache" in the Network tab and choose a slow network speed, like "Fast 4G", to see the effect clearly.

Key takeaways

  • loading="lazy" delays images and iframes until the user scrolls near them, but never use it on the hero image.
  • preconnect and preload help the browser find important files early. Use them sparingly.
  • defer runs scripts in order after the HTML is read, async runs them as soon as they arrive.
  • type="module" scripts are deferred automatically.
  • Core Web Vitals measure loading (LCP), reacting (INP) and visual stability (CLS).