Skip to Content
LoadingFind Images With Loading Lazy and Fetchpriority

Find images with loading lazy and fetchpriority

Chrome 38+ Edge 13+ Firefox 38+ Safari 9.1+ Details

Detects images that have both loading="lazy" and fetchpriority="high" - a contradictory combination that indicates a misconfiguration.

Why this is a problem

AttributePurposeWhen to use
loading="lazy"Defer loading until near viewportBelow-the-fold images
fetchpriority="high"Prioritize loading over other resourcesCritical above-the-fold images (LCP)

Using both together sends mixed signals to the browser:

  • “Don’t load this initially” (lazy)
  • “But when you do load it, make it high priority” (high)

This is contradictory because high-priority resources should load immediately, not be deferred.

“In reality you probably wouldn’t really want loading=lazy and fetchpriority=high at the same time. That would mean don’t load it initially, but when you start to load it, load it with high priority. Which is kind of contradictory!” - Barry Pollard (opens in a new tab)

Related: Use the Find Above The Fold Lazy Loaded Images snippet to detect lazy-loaded images in the viewport that may be hurting LCP.

Attribution

Based on the script (opens in a new tab) shared by Harry Roberts (opens in a new tab).

Snippet

Run it from an AI agent: the prompt uses the CLI to measure any URL and report the result.

// Find images with contradictory loading="lazy" and fetchpriority="high"
// https://webperf-snippets.net

(() => {
  function getSelector(node) {
    let sel = "";
    try {
      while (node && node.nodeType !== 9) {
        const el = node;
        const name = el.nodeName.toLowerCase();
        const part = el.id
          ? "#" + el.id
          : name +
            (el.classList && el.classList.value && el.classList.value.trim()
              ? "." + el.classList.value.trim().split(/\s+/).slice(0, 2).join(".")
              : "");
        if (sel.length + part.length > 80) return sel || part;
        sel = sel ? part + ">" + sel : part;
        if (el.id) break;
        node = el.parentNode;
      }
    } catch {}
    return sel;
  }

  function isInViewport(element) {
    const rect = element.getBoundingClientRect();
    return (
      rect.top < window.innerHeight &&
      rect.bottom > 0 &&
      rect.left < window.innerWidth &&
      rect.right > 0 &&
      rect.width > 0 &&
      rect.height > 0
    );
  }

  // Find LCP candidate for context
  const viewportImages = Array.from(document.querySelectorAll("img")).filter(
    (img) => isInViewport(img) && img.getBoundingClientRect().width > 0
  );

  let lcpCandidate = null;
  let maxArea = 0;
  viewportImages.forEach((img) => {
    const rect = img.getBoundingClientRect();
    const area = rect.width * rect.height;
    if (area > maxArea) {
      maxArea = area;
      lcpCandidate = img;
    }
  });

  // Find conflicting elements
  const conflictingElements = document.querySelectorAll(
    "[loading=lazy][fetchpriority=high]"
  );

  console.group("%c🔍 Lazy + Fetchpriority Conflict Check", "font-weight: bold; font-size: 14px;");

  if (conflictingElements.length === 0) {
    console.log(
      "%c✅ No conflicts found. No elements have both loading=\"lazy\" and fetchpriority=\"high\".",
      "color: #22c55e; font-weight: bold;"
    );
  } else {
    console.log(
      `%c⚠️ Found ${conflictingElements.length} element(s) with contradictory attributes`,
      "color: #ef4444; font-weight: bold; font-size: 14px;"
    );
    console.log("");

    // Analyze each element
    const tableData = Array.from(conflictingElements).map((el) => {
      const rect = el.getBoundingClientRect();
      const inViewport = isInViewport(el);
      const isLcp = el === lcpCandidate;

      return {
        selector: getSelector(el),
        dimensions: `${Math.round(rect.width)}×${Math.round(rect.height)}`,
        inViewport: inViewport ? "Yes" : "No",
        isLcpCandidate: isLcp ? "⚠️ Yes" : "No",
        src: (el.currentSrc || el.src || "").slice(-50),
        element: el,
      };
    });

    console.log("%c📋 Conflicting Elements:", "font-weight: bold;");
    console.table(tableData.map((item) => ({
      selector: item.selector,
      dimensions: item.dimensions,
      inViewport: item.inViewport,
      isLcpCandidate: item.isLcpCandidate,
      src: item.src,
    })));

    // Elements for inspection
    console.log("");
    console.log("%c🔎 Elements for inspection:", "font-weight: bold;");
    tableData.forEach(({ element, isLcpCandidate }, i) => {
      const marker = isLcpCandidate === "⚠️ Yes" ? " 🚨 LCP" : "";
      console.log(`${i + 1}.${marker}`, element);
    });

    // Recommendation
    console.log("");
    console.log("%c📝 How to fix:", "color: #3b82f6; font-weight: bold;");
    console.log("");

    tableData.forEach(({ selector, inViewport, isLcpCandidate }) => {
      console.log(`%c${selector}:`, "font-weight: bold;");
      if (inViewport === "Yes" || isLcpCandidate === "⚠️ Yes") {
        console.log("   → Image is in viewport. Remove loading=\"lazy\", keep fetchpriority=\"high\"");
        console.log(
          '%c   <img src="..." fetchpriority="high">',
          "font-family: monospace; color: #22c55e;"
        );
      } else {
        console.log("   → Image is outside viewport. Remove fetchpriority=\"high\", keep loading=\"lazy\"");
        console.log(
          '%c   <img src="..." loading="lazy">',
          "font-family: monospace; color: #22c55e;"
        );
      }
      console.log("");
    });

    console.log("%c💡 General rule:", "font-weight: bold;");
    console.log("   • Above the fold (LCP): fetchpriority=\"high\", NO lazy loading");
    console.log("   • Below the fold: loading=\"lazy\", NO fetchpriority");
  }

  console.groupEnd();

  return {
    script: "Find-Images-With-Lazy-and-Fetchpriority",
    status: "ok",
    count: conflictingElements.length,
    items: Array.from(conflictingElements).map((el) => {
      const rect = el.getBoundingClientRect();
      return {
        selector: getSelector(el),
        src: el.currentSrc || el.src || "",
        inViewport: isInViewport(el),
        isLcpCandidate: el === lcpCandidate,
        widthPx: Math.round(rect.width),
        heightPx: Math.round(rect.height),
      };
    }),
    issues: conflictingElements.length > 0
      ? [{ severity: "error", message: `${conflictingElements.length} element(s) have conflicting loading="lazy" and fetchpriority="high"` }]
      : [],
  };
})();

Understanding the results

The snippet checks for the contradictory combination and provides context:

For each conflicting element

FieldDescription
selectorCSS selector path to identify the element
dimensionsRendered width × height
inViewportWhether the image is currently visible
isLcpCandidateWhether this is the largest viewport image
srcImage source URL (truncated)

Recommendations

Based on the image position, the snippet suggests which attribute to keep:

Image PositionRecommendation
In viewport / LCPRemove loading="lazy", keep fetchpriority="high"
Outside viewportRemove fetchpriority="high", keep loading="lazy"

Best practices

For critical above-the-fold images (especially LCP)

<img src="hero.jpg" fetchpriority="high" alt="Hero image">

For below-the-fold images

<img src="content.jpg" loading="lazy" alt="Content image">

Never combine both

<!-- ❌ Contradictory - don't do this --> <img src="image.jpg" loading="lazy" fetchpriority="high" alt="...">

Browser support

The snippet needs these features to run.

FeatureChromeEdgeFirefoxSafari
Element.getBoundingClientRect (opens in a new tab) 2 12 3 4
HTMLImageElement.currentSrc (opens in a new tab) 38 13 38 9.1
All of the above 38 13 38 9.1

The snippet reports on these features. It runs without them, and its advice on each one applies where it is supported.

FeatureChromeEdgeFirefoxSafari
<img loading> 77 79 75 15.4
<img fetchpriority> (opens in a new tab) 101 101 132 17.2

Source: MDN browser compatibility data (opens in a new tab), version 8.1.4.

Further reading

Last updated on
MIT 2026 © Joan León | @nucliweb