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.
-
HTML Basics
- What is HTML: The Structure Layer of Every Web Page
- HTML Document Structure: DOCTYPE, html, head and body
- Elements, Tags and Attributes: The Vocabulary of HTML
- HTML Comments: Notes That Ship With Your Code
- Block Level and Inline Elements
- Writing and Running Your First HTML Page
- How a Browser Turns Markup Into a Page
- Text and Formatting
- Links and Navigation
- Images and Media
- Lists
- Tables
-
Forms
- Form Structure: form, action and method
- Input Types: Text, Email, Number, Date and the Rest
- Labels: The Most Important Element in a Form
- Checkboxes, Radio Buttons and Grouping
- select, option, optgroup and datalist
- textarea, File Uploads and Hidden Fields
- Buttons: submit, reset and button
- Built In Form Validation
- GET or POST: What Happens When a Form Is Submitted
- Semantic HTML
- HTML5 Features
- Head and Metadata
- HTML with CSS
- HTML with JavaScript
- Accessibility
-
HTML SEO
- How Google Works: Crawling, Indexing and Ranking
- SEO Friendly HTML Structure
- Titles and Descriptions That Earn Clicks
- Headings and Content Structure for Search
- Internal Linking and Anchor Text
- robots.txt and XML Sitemaps
- Canonical URLs and Duplicate Content
- Structured Data and JSON-LD
- Image SEO
- Core Web Vitals and Mobile Friendliness
- DevTools and Debugging
- Editor Productivity
- HTML Best Practices
- HTML Projects
- Advanced Projects
- Practice and Exams
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:
| Attribute | Answers |
|---|---|
loading | Should this be fetched now, or when the reader approaches it? |
decoding | May the browser decode it off the main thread? |
fetchpriority | How 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
pictureelement 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"requireswidthandheightto be useful, otherwise deferring the load causes the very layout shift you are trying to avoid.- Lazy loading works on
iframetoo. - 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
- Build a page with twenty images. Measure the transferred bytes on load, add
loading="lazy"below the fold, and measure again. - Run Lighthouse on a page whose hero is lazy loaded, then make it eager and high priority, and compare the largest paint time.
- Preload a responsive hero with
imagesrcsetand confirm in the Network panel that only one file was fetched. - Take one photograph, compress it at four quality levels, and find the point where you can first see a difference.