Skip to main content

decoding="async" Attribute on Non-Critical Image Elements

Issue No: 118

Category: Resource Hints & Performance Issue Type: Suggestion Priority: Standard


Description

Pages with multiple <img> elements should set decoding="async" on lazy-loaded images to offload image decoding off the main thread, reducing jank and improving Time to Interactive (TTI) for content-heavy pages.

The XeoPix crawler uses loading="lazy" as the scriptable proxy for non-critical images. A developer-declared loading="lazy" attribute is a definitive signal that the image is below-fold and not critical to initial render. The crawler checks which lazy images are missing decoding="async" — both attributes are fully readable from raw HTML without browser rendering.


How Do We Capture It

Where to Find

In the HTML document body:

<!-- Non-critical image with async decoding -->
<img src="/images/product-thumbnail.webp"
alt="Product thumbnail"
decoding="async"
loading="lazy">

HTML Extraction Flow

The XeoPix crawler downloads the raw HTML response and scans the document:

  1. Issues an HTTP GET to the target URL.
  2. Reads raw response bytes without JavaScript execution.
  3. Scans all <img> tags in the full HTML document.
  4. For each <img> tag, checks the decoding attribute value and the loading attribute value.
  5. Counts images with decoding="async", decoding="sync", decoding="auto", and images with no decoding attribute.
  6. Computes img_lazy_count — the number of <img loading="lazy"> tags.
  7. Computes img_lazy_without_decoding_async_count — the number of <img loading="lazy"> tags that do not also have decoding="async".

Validation Logic

The crawler validates:

  • Total count of <img> elements.
  • Count of images with loading="lazy".
  • Count of <img loading="lazy"> elements that also have decoding="async".
  • Count of <img loading="lazy"> elements that are missing decoding="async".

How to Get It

Crawler should:

  1. Parse full raw HTML document.
  2. Find all <img> tags.
  3. Check both decoding and loading attributes for each.
  4. Count: total imgs, imgs with loading="lazy", lazy imgs with decoding="async", lazy imgs without decoding="async".

What to Store

FieldTypeComment
img_total_countINTTotal number of img elements found in the page HTML
img_with_decoding_async_countINTNumber of img elements that have decoding="async"
img_without_decoding_async_countINTNumber of img elements that do not have decoding="async"
has_any_decoding_asyncBOOLEANWhether at least one img element declares decoding="async"
img_lazy_countINTNumber of img elements that declare loading="lazy"
img_lazy_without_decoding_async_countINTNumber of img elements with loading="lazy" that do not also have decoding="async"

Condition for Trigger

The following trigger rule fires only when lazy-loaded images exist but are missing the decoding=async attribute.

  • Trigger Rule 1: Lazy Images Found Without decoding="async"
    • Target Field: img_lazy_without_decoding_async_count
    • Evaluation Logic: > 0
    • Severity: SUGGESTION
    • Diagnostic Message: "One or more img elements with loading="lazy" do not declare decoding="async". Lazy images are below-fold by declaration. Adding decoding="async" to these images offloads their decoding to a background thread, reducing main-thread work during scroll."

Sources


Long Description

Image decoding is the process of translating compressed image bytes (JPEG, WebP, PNG, AVIF) into a pixel buffer the browser can display. By default, browsers decode images synchronously on the main thread. On pages with many images, this can cause the main thread to be blocked during decoding, resulting in janky scrolling, delayed user interaction responses, and increased Time to Interactive (TTI).

The decoding attribute controls when and where image decoding occurs:

ValueBehavior
syncDecode synchronously on the main thread; render is blocked until complete
asyncDecode on a background thread; rendering proceeds without waiting
autoBrowser decides (default behavior, typically sync for important images)

Without decoding="async":

Main thread: Parse HTML → Download → Decode image A → Decode image B → ... → Render
← blocked for each decode →

On pages with 20+ images, sequential main-thread decoding accumulates significant blocking time.

With decoding="async":

Main thread: Parse HTML → Signal decode → Render (continues without waiting)
Background thread: → Decode image A → Decode image B → Done

Decoding happens in parallel and does not block user interactions or the main render thread.

Common failure scenarios:

1. Image Grid Without decoding Attribute

A product listing page with 48 product thumbnail images — none with decoding="async". On lower-end mobile devices, scrolling through the grid causes main-thread janks as each new set of images decodes.

2. Mixing loading="lazy" Without decoding="async"

<!-- Partially optimized — lazy-loads but still blocks during decode -->
<img src="/thumb.webp" loading="lazy">

<!-- Fully optimized -->
<img src="/thumb.webp" loading="lazy" decoding="async">

loading="lazy" defers the fetch; decoding="async" defers the decoding. Both are needed for non-critical images.

3. decoding="async" on the LCP Hero Image

<!-- AVOID: LCP image should decode synchronously to minimize LCP time -->
<img src="/hero.webp" fetchpriority="high" decoding="async">

For the LCP hero image, decoding="async" may delay the image appearing on screen because the browser does not block rendering to wait for async decoding. The LCP image should omit decoding="async" or use decoding="sync" to ensure it renders as fast as possible.

4. decoding="sync" on Non-Critical Images

If decoding="sync" is explicitly set on below-fold images, the browser will block the main thread to decode them even after they enter the viewport during scroll, causing scroll jank.


How to Fix

Add decoding="async" to all non-critical <img> elements — especially those below the fold:

<!-- Non-critical images: use both lazy loading and async decoding -->
<img src="/images/product-1.webp"
alt="Product 1"
width="400"
height="300"
loading="lazy"
decoding="async">

<img src="/images/product-2.webp"
alt="Product 2"
width="400"
height="300"
loading="lazy"
decoding="async">

Guidelines:

  • Apply decoding="async" to all below-fold and non-critical images.
  • Do not apply to the LCP hero image — the hero should decode synchronously for the best LCP time.
  • Pair with loading="lazy" on below-fold images for maximum performance benefit.
  • decoding="async" is safe to use on all browsers that support it; unsupported browsers fall back to default (auto) behavior.
  • This is especially impactful on image-heavy pages: product grids, galleries, blog article lists, image carousels.