Skip to Content
LoadingPriority Hints Audit

Priority hints audit

Chrome 54+ Edge 17+ Firefox 45+ Safari 16.4+ Details

Audits all fetchpriority attribute usage across the page, covering non-image resources (scripts, preload links, iframes), fetchpriority="low" analysis, and conflicts between preload and low priority. Identifies the LCP candidate when it lacks fetchpriority="high".

Related: Use the Find Images With Lazy and Fetchpriority snippet to detect the specific loading="lazy" + fetchpriority="high" conflict on images.

What this snippet checks

CheckScope
Non-image resources with fetchpriority<script>, <link>, <iframe>
<link rel="preload"> + fetchpriority="low"Contradictory combination
fetchpriority="low" on early-loaded resourcesPossible misconfiguration
fetchpriority="high" on late-loaded resourcesInvestigate blocking dependencies
LCP candidate image without fetchpriority="high"Missed LCP optimization

Why fetchpriority needs validation

fetchpriority was introduced in 2022 and hints to the browser how to prioritize a resource fetch relative to others of the same type. Incorrect usage sends conflicting signals:

CombinationProblem
rel="preload" + fetchpriority="low"preload declares a resource as critical for the current page; low contradicts that
fetchpriority="low" on a resource loaded in the first 500msIf it loads that early, the browser may already consider it critical
fetchpriority="high" on a resource loaded after 3sHigh priority did not help, investigate render-blocking dependencies
LCP image without fetchpriority="high"Missed opportunity to improve Largest Contentful Paint

See how priority reorders a load

Compare the same page with different fetchpriority values and watch when the LCP image finishes, and which of these combinations the snippet flags:

Switch scenarios and replay the load to see how the bandwidth is shared. Open demo in a new tab ↗

Snippet

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

// Priority Hints Audit
// https://webperf-snippets.nucliweb.net

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

  function findLcpCandidate() {
    const imgs = Array.from(document.querySelectorAll("img")).filter(isInViewport);
    let candidate = null;
    let maxArea = 0;
    imgs.forEach((img) => {
      const { width, height } = img.getBoundingClientRect();
      const area = width * height;
      if (area > maxArea) {
        maxArea = area;
        candidate = img;
      }
    });
    return candidate;
  }

  function shortUrl(el) {
    const src = el.src || el.href || el.getAttribute("href") || "";
    return src.split("/").pop()?.split("?")[0] || src.slice(-50) || "(unknown)";
  }

  // Build performance entry map for timing lookups
  const entryMap = new Map();
  performance.getEntriesByType("resource").forEach((e) => {
    try {
      entryMap.set(new URL(e.name, location.origin).href, e);
    } catch {}
  });

  function getEntry(el) {
    const src = el.src || el.href || el.getAttribute("href") || "";
    if (!src) return null;
    try {
      return entryMap.get(new URL(src, location.origin).href) || null;
    } catch {
      return null;
    }
  }

  // All elements with fetchpriority
  const allElements = Array.from(document.querySelectorAll("[fetchpriority]"));
  // Exclude images — covered by "Find Images With Lazy and Fetchpriority"
  const nonImageElements = allElements.filter((el) => el.tagName !== "IMG");
  const images = allElements.filter((el) => el.tagName === "IMG");
  const preloadsWithPriority = Array.from(
    document.querySelectorAll('link[rel="preload"][fetchpriority]')
  );
  const lcpCandidate = findLcpCandidate();

  const issues = [];

  // Issue 1: <link rel="preload"> with fetchpriority="low" (contradictory)
  preloadsWithPriority
    .filter((link) => link.getAttribute("fetchpriority") === "low")
    .forEach((link) => {
      issues.push({
        severity: "error",
        element: link,
        resource: shortUrl(link),
        message: 'preload + fetchpriority="low" is contradictory',
        detail:
          'preload signals this resource is critical for the current page; fetchpriority="low" contradicts that.',
        fix: 'Remove fetchpriority="low", or use fetchpriority="high" if the resource is truly critical',
      });
    });

  // Issue 2: fetchpriority="low" on resources loaded in the first 500ms
  allElements
    .filter((el) => el.getAttribute("fetchpriority") === "low")
    .forEach((el) => {
      const entry = getEntry(el);
      if (entry && entry.startTime < 500) {
        issues.push({
          severity: "warning",
          element: el,
          resource: shortUrl(el),
          message: `fetchpriority="low" but loaded at ${entry.startTime.toFixed(0)}ms`,
          detail:
            "This resource loaded very early. If it's genuinely non-critical, the browser is deprioritizing it correctly. Otherwise, reconsider the priority.",
          fix: 'Verify the resource is non-critical, or remove fetchpriority="low"',
        });
      }
    });

  // Issue 3: fetchpriority="high" on resources loaded after 3000ms
  allElements
    .filter((el) => el.getAttribute("fetchpriority") === "high")
    .forEach((el) => {
      const entry = getEntry(el);
      if (entry && entry.startTime > 3000) {
        issues.push({
          severity: "warning",
          element: el,
          resource: shortUrl(el),
          message: `fetchpriority="high" but loaded at ${entry.startTime.toFixed(0)}ms`,
          detail:
            "High-priority resources should load early. A late start may indicate render-blocking dependencies or incorrect placement in the document.",
          fix: "Move the element earlier in the HTML or investigate render-blocking dependencies",
        });
      }
    });

  // Issue 4: LCP candidate image without fetchpriority="high"
  if (lcpCandidate && lcpCandidate.getAttribute("fetchpriority") !== "high") {
    const fp = lcpCandidate.getAttribute("fetchpriority");
    issues.push({
      severity: "warning",
      element: lcpCandidate,
      resource:
        (lcpCandidate.currentSrc || lcpCandidate.src).split("/").pop()?.split("?")[0] ||
        "LCP image",
      message: `LCP candidate image ${fp ? `has fetchpriority="${fp}"` : "has no fetchpriority set"}`,
      detail:
        'The largest visible image is likely the LCP element and benefits most from fetchpriority="high".',
      fix: 'Add fetchpriority="high" to the LCP image to improve Largest Contentful Paint',
    });
  }

  // Display results
  console.group("%c🎯 Priority Hints Audit", "font-weight: bold; font-size: 14px;");

  const highCount = allElements.filter(
    (el) => el.getAttribute("fetchpriority") === "high"
  ).length;
  const lowCount = allElements.filter(
    (el) => el.getAttribute("fetchpriority") === "low"
  ).length;
  const autoCount = allElements.filter(
    (el) => el.getAttribute("fetchpriority") === "auto"
  ).length;

  console.log("");
  console.log("%cInventory:", "font-weight: bold;");
  console.log(`   Total elements with fetchpriority: ${allElements.length}`);
  console.log(
    `     • Images: ${images.length} (see "Find Images With Lazy and Fetchpriority" for image-specific checks)`
  );
  console.log(`     • Non-image resources: ${nonImageElements.length}`);
  console.log(`     • Preloads with fetchpriority: ${preloadsWithPriority.length}`);
  console.log(
    `   Priority breakdown: high=${highCount}, low=${lowCount}, auto=${autoCount}`
  );

  if (allElements.length === 0 && !lcpCandidate) {
    console.log("");
    console.log(
      "%cℹ️ No fetchpriority attributes found on this page.",
      "color: #3b82f6;"
    );
    console.groupEnd();
    return { script: "Priority-Hints-Audit", status: "ok", count: 0, items: [], issues: [] };
  }

  // LCP candidate status
  if (lcpCandidate) {
    const fp = lcpCandidate.getAttribute("fetchpriority");
    const status =
      fp === "high"
        ? '✅ fetchpriority="high"'
        : fp
        ? `⚠️ fetchpriority="${fp}"`
        : "⚠️ No fetchpriority set";
    console.log("");
    console.log("%cLCP Candidate:", "font-weight: bold;");
    console.log(`   ${status}`);
    console.log("   Element:", lcpCandidate);
  }

  // Non-image resources table
  if (nonImageElements.length > 0) {
    console.log("");
    console.group(
      `%c📋 Non-Image Resources with fetchpriority (${nonImageElements.length})`,
      "font-weight: bold;"
    );

    console.table(
      nonImageElements.map((el) => {
        const entry = getEntry(el);
        const rel = el.getAttribute("rel") || "";
        const as = el.getAttribute("as") || "";
        const tag =
          el.tagName.toLowerCase() +
          (rel ? `[rel="${rel}"]` : "") +
          (as ? `[as="${as}"]` : "");
        return {
          Tag: tag,
          Priority: el.getAttribute("fetchpriority"),
          "Start (ms)": entry ? entry.startTime.toFixed(0) : "N/A",
          "Size (KB)":
            entry?.decodedBodySize
              ? (entry.decodedBodySize / 1024).toFixed(1)
              : "N/A",
          Resource: shortUrl(el),
        };
      })
    );

    console.log("%c🔎 Elements:", "font-weight: bold;");
    nonImageElements.forEach((el, i) => console.log(`${i + 1}.`, el));
    console.groupEnd();
  }

  // Preload links with fetchpriority
  if (preloadsWithPriority.length > 0) {
    console.log("");
    console.group(
      `%c🔗 Preload Links with fetchpriority (${preloadsWithPriority.length})`,
      "font-weight: bold;"
    );

    console.table(
      preloadsWithPriority.map((link) => {
        const fp = link.getAttribute("fetchpriority");
        const entry = getEntry(link);
        return {
          fetchpriority: fp,
          as: link.getAttribute("as") || "(missing)",
          Status:
            fp === "low" ? "⚠️ Conflict" : fp === "high" ? "✅ Good" : "ℹ️ Auto",
          "Start (ms)": entry ? entry.startTime.toFixed(0) : "N/A",
          Resource: shortUrl(link),
        };
      })
    );

    console.groupEnd();
  }

  // Issues
  const errors = issues.filter((i) => i.severity === "error");
  const warnings = issues.filter((i) => i.severity === "warning");

  if (issues.length > 0) {
    console.log("");
    console.group(
      `%c⚠️ Issues Found (${errors.length} errors, ${warnings.length} warnings)`,
      "color: #ef4444; font-weight: bold;"
    );

    [...errors, ...warnings].forEach((issue) => {
      const color = issue.severity === "error" ? "#ef4444" : "#f59e0b";
      const icon = issue.severity === "error" ? "🔴" : "⚠️";
      console.log("");
      console.log(
        `%c${icon} ${issue.message}`,
        `font-weight: bold; color: ${color};`
      );
      console.log(`   ${issue.detail}`);
      console.log(`   Fix: ${issue.fix}`);
      console.log("   Element:", issue.element);
    });

    console.groupEnd();
  } else {
    console.log("");
    console.log(
      "%c✅ No priority hints issues detected.",
      "color: #22c55e; font-weight: bold;"
    );
  }

  // Quick reference
  console.log("");
  console.group(
    "%c📝 fetchpriority Quick Reference",
    "color: #3b82f6; font-weight: bold;"
  );
  console.log("");
  console.log("%c  ✅ LCP hero image:", "color: #22c55e;");
  console.log(
    '%c  <img src="hero.webp" fetchpriority="high" alt="...">',
    "font-family: monospace;"
  );
  console.log("");
  console.log("%c  ✅ Preloaded critical font:", "color: #22c55e;");
  console.log(
    '%c  <link rel="preload" href="font.woff2" as="font" fetchpriority="high" crossorigin>',
    "font-family: monospace;"
  );
  console.log("");
  console.log("%c  ✅ Below-fold image:", "color: #22c55e;");
  console.log(
    '%c  <img src="content.jpg" loading="lazy" fetchpriority="low" alt="...">',
    "font-family: monospace;"
  );
  console.log("");
  console.log("%c  ❌ Contradictory — preload + low priority:", "color: #ef4444;");
  console.log(
    '%c  <link rel="preload" href="font.woff2" as="font" fetchpriority="low">',
    "font-family: monospace;"
  );
  console.groupEnd();

  console.log("");
  console.log(
    '%c💡 Related: "Find Images With Lazy and Fetchpriority" detects the lazy+high conflict on images. https://webperf-snippets.nucliweb.net/Loading/Find-Images-With-Lazy-and-Fetchpriority',
    "color: #6b7280;"
  );

  console.groupEnd();

  return {
    script: "Priority-Hints-Audit",
    status: "ok",
    count: allElements.length,
    details: {
      highCount,
      lowCount,
      autoCount,
      preloadsWithPriorityCount: preloadsWithPriority.length,
      lcpCandidateHasFetchpriority: lcpCandidate ? lcpCandidate.getAttribute("fetchpriority") === "high" : null,
    },
    items: allElements.map(el => ({
      tag: el.tagName.toLowerCase(),
      fetchpriority: el.getAttribute("fetchpriority"),
      resource: (el.src || el.href || el.getAttribute("href") || "").split("/").pop()?.split("?")[0] || "",
    })),
    issues: issues.map(i => ({
      severity: i.severity,
      message: i.fix ? `${i.message.replace(/\.$/, "")}. Fix: ${i.fix}` : i.message,
    })),
  };
})();

Understanding the results

Inventory section

FieldDescription
Total elementsAll elements with a fetchpriority attribute
ImagesExcluded from non-image checks (see related snippet)
Non-image resources<script>, <link>, <iframe> with fetchpriority
Preloads with fetchpriority<link rel="preload"> that also set priority
Priority breakdownCount of high, low, and auto values

Non-image resources table

ColumnDescription
TagElement tag and key attributes (rel, as)
PriorityThe fetchpriority value set
Start (ms)When the browser started fetching (from Performance API)
Size (KB)Decompressed resource size
ResourceFilename extracted from the URL
ColumnDescription
fetchpriorityValue set on the preload link
asResource type (font, image, script, etc.)
Status✅ Good (high), ⚠️ Conflict (low), ℹ️ Auto
Start (ms)Load start time from the Performance API
ResourceFilename

Issues detected

IssueSeverityExplanation
preload + fetchpriority="low"ErrorContradictory signals, remove or change to high
fetchpriority="low" loaded in <500msWarningResource may be more critical than declared
fetchpriority="high" loaded after 3000msWarningHigh priority did not help, investigate dependencies
LCP candidate without fetchpriority="high"WarningMissed opportunity to improve LCP

Best practices

LCP image, always set fetchpriority="high":

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

Preloaded critical resource, pair preload with fetchpriority="high":

<link rel="preload" href="critical-font.woff2" as="font" fetchpriority="high" crossorigin>

Below-fold image, loading="lazy" with optional fetchpriority="low":

<img src="gallery.jpg" loading="lazy" fetchpriority="low" alt="Gallery image">

Never combine preload with fetchpriority="low"

<!-- ❌ Contradictory --> <link rel="preload" href="font.woff2" as="font" fetchpriority="low"> <!-- ✅ Correct --> <link rel="preload" href="font.woff2" as="font" fetchpriority="high" crossorigin>

Browser support

The snippet needs these features to run.

FeatureChromeEdgeFirefoxSafari
Element.getBoundingClientRect (opens in a new tab) 2 12 3 4
PerformanceResourceTiming.decodedBodySize (opens in a new tab) 54 17 45 16.4
All of the above 54 17 45 16.4

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

FeatureChromeEdgeFirefoxSafari
<img fetchpriority> (opens in a new tab) 101 101 132 17.2
<link fetchpriority> (opens in a new tab) 101 101 132 17.2
<script 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