# Find render-blocking resources

> Source: <https://webperf-snippets.net/Loading/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).

### Why this matters

Render-blocking resources are the primary cause of slow initial page renders. Users see a blank white screen while CSS and JavaScript files download and parse. On slow connections or mobile networks, this can add several seconds to your load time. Eliminating or deferring render-blocking resources is one of the highest-impact optimizations you can make.

### What are render-blocking resources?

| Resource Type  | When it blocks rendering                                                   |
| -------------- | -------------------------------------------------------------------------- |
| **CSS**        | All `<link rel="stylesheet">` in `<head>` block rendering by default       |
| **JavaScript** | Scripts 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

### Render-blocking timeline

Interactive demo: [Interactive timeline comparing how render-blocking resources delay First Contentful Paint against an optimized setup with inlined critical CSS and deferred scripts](https://webperf-snippets.net/demos/render-blocking-timeline.html). Switch between the render-blocking and optimized setups, then step through to see when the first paint can happen in each.

> **Warning:** **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

```js
// Find render-blocking resources
// https://webperf-snippets.net

(() => {
  // @shared isOwnRequest
  // True for the Resource Timing entry of a request a snippet made with ownRequestUrl: the page
  // did not make it, so no snippet counts it.
  function isOwnRequest(entry) {
    return entry.name.endsWith("#webperf-snippets");
  }
  // @end-shared isOwnRequest

  // Check browser support
  const testEntry = performance.getEntriesByType("resource").filter((e) => !isOwnRequest(e))[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 { script: "Find-render-blocking-resources", status: "unsupported", error: "renderBlockingStatus API requires Chrome 107+" };
  }

  const blockingResources = performance
    .getEntriesByType("resource").filter((e) => !isOwnRequest(e))
    .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);

  const lastBlockingEnd = blockingResources.length
    ? Math.max(...blockingResources.map((r) => r.responseEnd))
    : 0;
  const totalSizeBytes = blockingResources.reduce((sum, r) => sum + r.size, 0);
  const byType = blockingResources.reduce((acc, r) => {
    acc[r.type] = (acc[r.type] || 0) + 1;
    return acc;
  }, {});

  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 {
    // 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 (totalSizeBytes > 0) {
      const sizeKB = (totalSizeBytes / 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();

  return {
    script: "Find-render-blocking-resources",
    status: "ok",
    count: blockingResources.length,
    details: {
      totalBlockingUntilMs: Math.round(lastBlockingEnd),
      totalSizeBytes,
      byType,
    },
    items: blockingResources.map((r) => ({
      type: r.type,
      url: r.name,
      shortName: r.shortName,
      responseEndMs: Math.round(r.responseEnd),
      durationMs: Math.round(r.duration),
      sizeBytes: r.size,
    })),
    issues: blockingResources.length > 0
      ? [{ severity: "error", message: `${blockingResources.length} render-blocking resource(s) delay rendering until ${Math.round(lastBlockingEnd)}ms` }]
      : [],
  };
})();
```

### Understanding the results

### Summary metrics

| Metric                       | Description                                                                                  |
| ---------------------------- | -------------------------------------------------------------------------------------------- |
| **Rendering blocked until**  | Time when the last blocking resource finishes loading - rendering can start after this point |
| **Total blocking resources** | Number of resources blocking initial render                                                  |
| **Total size**               | Combined transfer size of all blocking resources                                             |
| **By type**                  | Breakdown 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.

### Optimization decision tree

Diagram: Decision tree for a render-blocking resource: critical CSS is inlined, other CSS loads asynchronously, critical small scripts are inlined, other critical scripts move to the end of body, non-critical scripts use defer, and fonts use font-display swap plus preload

### Common render-blocking patterns

### Scripts in `<head>` without defer/async

```html
<!-- ❌ Render-blocking -->
<head>
  <script src="app.js"></script>
</head>

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

### External stylesheets

```html
<!-- ❌ 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

```css
/* ❌ 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;
}
```

### Browser support

| Feature                                                                                                                                     | Chrome |  Edge | Firefox | Safari |
| ------------------------------------------------------------------------------------------------------------------------------------------- | :----: | :---: | :-----: | :----: |
| [PerformanceResourceTiming.renderBlockingStatus](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/renderBlockingStatus) |  ✅ 107 | ✅ 107 |    ❌    |    ❌   |

Source: [MDN browser compatibility data](https://github.com/mdn/browser-compat-data), version 8.1.4.

### Further reading

- [Eliminate render-blocking resources](https://developer.chrome.com/docs/lighthouse/performance/render-blocking-resources) | Chrome Developers
- [Render-blocking CSS](https://web.dev/articles/critical-rendering-path/render-blocking-css) | web.dev
- [Efficiently load JavaScript](https://web.dev/articles/efficiently-load-third-party-javascript) | web.dev
- [Preload critical assets](https://web.dev/articles/preload-critical-assets) | web.dev
- [FCP](https://webperf-snippets.net/Loading/FCP.md) | See how render-blocking resources impact First Contentful Paint
