Lazy Loading and Image Performance

Images are usually the heaviest thing on a page. loading, decoding, fetchpriority and preload are four attributes that decide how fast it feels.

Concept

On a typical content page, images account for more transferred bytes than everything else combined. Getting them right is the single largest performance win available in plain HTML, with no build tooling at all.

Four attributes control the behaviour, and each answers a different question:

AttributeAnswers
loadingShould this be fetched now, or when the reader approaches it?
decodingMay the browser decode it off the main thread?
fetchpriorityHow urgent is this compared with everything else being fetched?
rel="preload"Start fetching before the parser reaches the element at all.

loading

<!-- fetched only as the reader scrolls near it -->
<img src="/images/gallery-7.jpg" alt="A stall selling brass lamps" width="800" height="600" loading="lazy">

<!-- fetched immediately, the default -->
<img src="/images/hero.jpg" alt="The main gate at sunrise" width="1600" height="900" loading="eager">

The rule that matters: never lazy load the image at the top of the page. On most content pages the hero image is the Largest Contentful Paint element, and Google measures how long it takes to appear. Deferring it delays the exact thing being measured, which is why loading="lazy" on a hero image usually makes a page score worse rather than better.

Everything below the first screen should be lazy. The browser applies a generous margin, so images load before they scroll into view and the reader rarely sees a gap.

decoding

<img src="/images/wide.jpg" alt="A river at low water" width="2000" height="1200" decoding="async">

Decoding a large image is real work and by default it happens on the main thread, where it competes with everything else. async allows the browser to do it elsewhere and paint the image when it is ready. It is safe on almost every image and costs nothing.

fetchpriority

<!-- the one image that matters most -->
<img src="/images/hero.jpg" alt="The main gate at sunrise"
     width="1600" height="900" fetchpriority="high">

<!-- a decorative image that can wait -->
<img src="/images/texture.png" alt="" width="400" height="400" fetchpriority="low">

Browsers guess at priority, and they guess before layout, so an image at the top of the markup that turns out to be small may still be treated as important. fetchpriority="high" on the hero is a direct instruction and typically shaves a meaningful amount off the largest paint time.

Use it on one image per page. Marking everything high is the same as marking nothing high.

preload

<head>
  <link rel="preload" as="image" href="/images/hero.jpg" fetchpriority="high">
</head>

The parser normally cannot start fetching the hero until it reaches the img tag, which may be after a stylesheet and some markup. A preload in the head starts it immediately.

For a responsive hero, the preload must match the candidate the browser will actually choose, or you download two files instead of one:

<link rel="preload" as="image"
      href="/images/hero-800.jpg"
      imagesrcset="/images/hero-400.jpg 400w, /images/hero-800.jpg 800w, /images/hero-1600.jpg 1600w"
      imagesizes="100vw">

A realistic page

<head>
  <link rel="preload" as="image" href="/images/hero-800.jpg"
        imagesrcset="/images/hero-400.jpg 400w, /images/hero-800.jpg 800w, /images/hero-1600.jpg 1600w"
        imagesizes="100vw" fetchpriority="high">
</head>
<body>
  <img src="/images/hero-800.jpg"
       srcset="/images/hero-400.jpg 400w, /images/hero-800.jpg 800w, /images/hero-1600.jpg 1600w"
       sizes="100vw"
       alt="The main gate at sunrise"
       width="1600" height="900"
       fetchpriority="high" decoding="async">

  <!-- everything further down -->
  <img src="/images/gallery-1.jpg" alt="Brass lamps on a workbench"
       width="800" height="600" loading="lazy" decoding="async">
</body>

What else moves the needle

  • Compress properly. Eighty percent JPEG quality is usually indistinguishable from a hundred and roughly half the size.
  • Serve modern formats. AVIF and WebP through a picture element cut twenty five to fifty percent.
  • Do not ship oversized files. A four thousand pixel photograph displayed at eight hundred pixels wastes most of what it downloads.
  • Use SVG for anything drawn. Usually a few kilobytes, and sharp at every zoom level.
  • Always set width and height. Layout shift is measured and it counts against you.
  • Cache aggressively. Version image file names and set a long cache lifetime.

Important rules

  • loading="lazy" requires width and height to be useful, otherwise deferring the load causes the very layout shift you are trying to avoid.
  • Lazy loading works on iframe too.
  • Native lazy loading needs no JavaScript and no library.
  • A preload that is never used is pure waste and the browser will warn about it in the console.
  • Priority hints are hints. The browser may still decide otherwise.

Common mistakes

  • Lazy loading the hero image and damaging the largest paint measurement.
  • Adding loading="lazy" without dimensions.
  • Marking a dozen images as high priority.
  • Preloading an image that the responsive selection then rejects, downloading both.
  • Assuming lazy loading fixes a page whose real problem is uncompressed four megabyte files.
  • Lazy loading images that are above the fold on a phone but below it on a desktop.

Best practices

  • Eager and high priority for the hero; lazy and async for everything else.
  • One fetchpriority="high" per page at most.
  • Compress and resize before upload, always.
  • Measure with the Network panel and a Lighthouse run rather than guessing.
  • Test on a throttled connection, because that is the experience most readers actually have.

Practice

  1. Build a page with twenty images. Measure the transferred bytes on load, add loading="lazy" below the fold, and measure again.
  2. Run Lighthouse on a page whose hero is lazy loaded, then make it eager and high priority, and compare the largest paint time.
  3. Preload a responsive hero with imagesrcset and confirm in the Network panel that only one file was fetched.
  4. Take one photograph, compress it at four quality levels, and find the point where you can first see a difference.

Useful resources

Hand picked references for this topic
Written by Lorens Mishra

Default administrator account created by the installer.

Continue reading

All HTML notes →
HTML

Audio and Video in HTML

Native media playback with no plugin. Learn the controls, the source fallback pattern, autoplay rules that will catch you out, and why captions are no...

Read more

Discussion

0 comments
Sign in to join the discussion.

No comments yet. Be the first to say something.