Loading
Find Render Blocking Resources

Find render-blocking resources

Identifies resources that block the browser from rendering the page. These resources must be fully downloaded and processed before the browser can display any content, directly impacting First Contentful Paint (FCP) and Largest Contentful Paint (LCP).

What are render-blocking resources?

Resource TypeWhen it blocks rendering
CSSAll <link rel="stylesheet"> in <head> block rendering by default
JavaScriptScripts without async or defer in <head> block parsing and rendering
Fonts@font-face fonts block text rendering until loaded

Impact on performance:

  • Delays First Contentful Paint (FCP)
  • Can delay Largest Contentful Paint (LCP)
  • Users see a blank page while resources load
  • Particularly harmful on slow connections

Browser Support: The renderBlockingStatus property is currently Chromium-only (Chrome 107+, Edge 107+, Opera 93+). Firefox and Safari don't support this API yet.

Snippet

// Find render-blocking resources
// https://webperf-snippets.nucliweb.net
 
(() => {
  // Check browser support
  const testEntry = performance.getEntriesByType("resource")[0];
  if (testEntry && !("renderBlockingStatus" in testEntry)) {
    console.log(
      "%c⚠️ renderBlockingStatus not supported in this browser",
      "color: #f59e0b; font-weight: bold;"
    );
    console.log("This API is currently Chromium-only (Chrome 107+, Edge 107+).");
    console.log("Try using Chrome or Edge to analyze render-blocking resources.");
    return;
  }
 
  const blockingResources = performance
    .getEntriesByType("resource")
    .filter((entry) => entry.renderBlockingStatus === "blocking")
    .map((entry) => {
      const url = new URL(entry.name);
      return {
        name: entry.name,
        shortName: url.pathname.split("/").pop() || url.pathname,
        type: entry.initiatorType,
        startTime: entry.startTime,
        duration: entry.duration,
        responseEnd: entry.responseEnd,
        size: entry.transferSize || 0,
      };
    })
    .sort((a, b) => b.responseEnd - a.responseEnd);
 
  console.group("%c🚧 Render-Blocking Resources", "font-weight: bold; font-size: 14px;");
 
  if (blockingResources.length === 0) {
    console.log(
      "%c✅ No render-blocking resources found!",
      "color: #22c55e; font-weight: bold;"
    );
    console.log("The page has no resources blocking initial render.");
    console.log("");
    console.log("%c💡 This could mean:", "font-weight: bold;");
    console.log("   • CSS is inlined or loaded asynchronously");
    console.log("   • Scripts use async or defer attributes");
    console.log("   • Critical resources are optimized");
  } else {
    // Calculate metrics
    const lastBlockingEnd = Math.max(...blockingResources.map((r) => r.responseEnd));
    const totalSize = blockingResources.reduce((sum, r) => sum + r.size, 0);
    const byType = blockingResources.reduce((acc, r) => {
      acc[r.type] = (acc[r.type] || 0) + 1;
      return acc;
    }, {});
 
    // Summary
    console.log(
      `%c⚠️ Found ${blockingResources.length} render-blocking resource(s)`,
      "color: #ef4444; font-weight: bold; font-size: 14px;"
    );
    console.log("");
 
    console.log("%c📊 Impact Summary:", "font-weight: bold;");
    console.log(`   Rendering blocked until: ${lastBlockingEnd.toFixed(0)}ms`);
    console.log(`   Total blocking resources: ${blockingResources.length}`);
    if (totalSize > 0) {
      const sizeKB = (totalSize / 1024).toFixed(1);
      console.log(`   Total size: ${sizeKB} KB`);
    }
    console.log(`   By type: ${Object.entries(byType).map(([k, v]) => `${k} (${v})`).join(", ")}`);
 
    // Timeline visualization
    console.log("");
    console.log("%c⏱️ Timeline (sorted by completion):", "font-weight: bold;");
 
    const formatBar = (end, max) => {
      const pct = (end / max) * 100;
      const width = Math.round(pct / 5);
      return "█".repeat(width) + "░".repeat(20 - width);
    };
 
    blockingResources.forEach((r) => {
      const bar = formatBar(r.responseEnd, lastBlockingEnd);
      const sizeInfo = r.size > 0 ? ` (${(r.size / 1024).toFixed(1)} KB)` : "";
      console.log(
        `   ${r.type.padEnd(8)} ${r.responseEnd.toFixed(0).padStart(5)}ms ${bar} ${r.shortName}${sizeInfo}`
      );
    });
 
    // Table with details
    console.log("");
    console.log("%c📋 Details:", "font-weight: bold;");
    const tableData = blockingResources.map((r) => ({
      Type: r.type,
      "Response End": r.responseEnd.toFixed(0) + "ms",
      Duration: r.duration.toFixed(0) + "ms",
      Size: r.size > 0 ? (r.size / 1024).toFixed(1) + " KB" : "N/A",
      Resource: r.name.length > 60 ? "..." + r.name.slice(-57) : r.name,
    }));
    console.table(tableData);
 
    // Recommendations
    console.log("");
    console.log("%c📝 How to fix:", "color: #3b82f6; font-weight: bold;");
    console.log("");
 
    if (byType.script) {
      console.log("%c   Scripts:", "font-weight: bold;");
      console.log("   • Add 'defer' for scripts that don't need to run immediately");
      console.log("   • Add 'async' for independent scripts (analytics, etc.)");
      console.log("   • Move non-critical scripts to end of <body>");
      console.log('%c   <script src="app.js" defer></script>', "font-family: monospace; color: #22c55e;");
      console.log("");
    }
 
    if (byType.link || byType.css) {
      console.log("%c   CSS:", "font-weight: bold;");
      console.log("   • Inline critical CSS in <head>");
      console.log("   • Load non-critical CSS asynchronously with media='print' trick");
      console.log("   • Use <link rel='preload'> for critical stylesheets");
      console.log('%c   <link rel="preload" href="style.css" as="style" onload="this.rel=\'stylesheet\'">', "font-family: monospace; color: #22c55e;");
      console.log("");
    }
 
    if (byType.font) {
      console.log("%c   Fonts:", "font-weight: bold;");
      console.log("   • Use font-display: swap in @font-face");
      console.log("   • Preload critical fonts");
      console.log("   • Consider system font stack for initial render");
      console.log('%c   <link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>', "font-family: monospace; color: #22c55e;");
      console.log("");
    }
  }
 
  console.groupEnd();
})();

Understanding the Results

Summary Metrics:

MetricDescription
Rendering blocked untilTime when the last blocking resource finishes loading - rendering can start after this point
Total blocking resourcesNumber of resources blocking initial render
Total sizeCombined transfer size of all blocking resources
By typeBreakdown by resource type (script, link/css, font)

Timeline Visualization:

Shows each resource with:

  • Resource type
  • When it finished loading (responseEnd)
  • Visual bar showing relative timing
  • Filename and size

Resources are sorted by completion time (latest first), so you can see which resource is the last to complete and thus determines when rendering can start.

Common Render-Blocking Patterns

Scripts in <head> without defer/async:

<!-- ❌ Render-blocking -->
<head>
  <script src="app.js"></script>
</head>
 
<!-- ✅ Non-blocking -->
<head>
  <script src="app.js" defer></script>
</head>

External stylesheets:

<!-- ❌ Render-blocking (but often necessary) -->
<link rel="stylesheet" href="styles.css">
 
<!-- ✅ Preload + async load for non-critical CSS -->
<link rel="preload" href="non-critical.css" as="style" onload="this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="non-critical.css"></noscript>

Web fonts:

/* ❌ Blocks text rendering */
@font-face {
  font-family: 'MyFont';
  src: url('font.woff2') format('woff2');
}
 
/* ✅ Shows fallback font immediately */
@font-face {
  font-family: 'MyFont';
  src: url('font.woff2') format('woff2');
  font-display: swap;
}

Further Reading