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:
- Issues an HTTP GET to the target URL.
- Reads raw response bytes without JavaScript execution.
- Scans all
<img>tags in the full HTML document. - For each
<img>tag, checks thedecodingattribute value and theloadingattribute value. - Counts images with
decoding="async",decoding="sync",decoding="auto", and images with nodecodingattribute. - Computes
img_lazy_count— the number of<img loading="lazy">tags. - Computes
img_lazy_without_decoding_async_count— the number of<img loading="lazy">tags that do not also havedecoding="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 havedecoding="async". - Count of
<img loading="lazy">elements that are missingdecoding="async".
How to Get It
Crawler should:
- Parse full raw HTML document.
- Find all
<img>tags. - Check both
decodingandloadingattributes for each. - Count: total imgs, imgs with
loading="lazy", lazy imgs withdecoding="async", lazy imgs withoutdecoding="async".
What to Store
| Field | Type | Comment |
|---|---|---|
img_total_count | INT | Total number of img elements found in the page HTML |
img_with_decoding_async_count | INT | Number of img elements that have decoding="async" |
img_without_decoding_async_count | INT | Number of img elements that do not have decoding="async" |
has_any_decoding_async | BOOLEAN | Whether at least one img element declares decoding="async" |
img_lazy_count | INT | Number of img elements that declare loading="lazy" |
img_lazy_without_decoding_async_count | INT | Number 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."
- Target Field:
Sources
- MDN — HTMLImageElement: decoding property
- MDN — img: decoding attribute
- web.dev — Image performance: decoding attribute
- WHATWG HTML Standard — decoding attribute
- web.dev — Time to Interactive (TTI)
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:
| Value | Behavior |
|---|---|
sync | Decode synchronously on the main thread; render is blocked until complete |
async | Decode on a background thread; rendering proceeds without waiting |
auto | Browser 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.