Skip to content

Render-Blocking Resources — Eliminating Blocks Guide

DodaTech Updated 2026-06-23 8 min read

In this tutorial, you will learn how to identify, analyze, and eliminate render-blocking resources that delay the first paint of your web pages. Render-blocking resources are CSS and JavaScript files that the browser must download, parse, and execute before it can render any content. DodaTech eliminated 90 percent of render-blocking resources on its main site, reducing FCP by 1.2 seconds.

What You Will Learn

  • How the browser determines which resources are render-blocking
  • How to identify render-blocking resources using Lighthouse and DevTools
  • How to eliminate render-blocking CSS with inlining and async loading
  • How to eliminate render-blocking JavaScript with defer and async

Why It Matters

Render-blocking resources are the number one cause of slow first paint. Each blocking resource adds at least one network round trip to the critical path. Google Lighthouse flags every blocking resource as an opportunity, and fixing them often produces the largest performance gains.

Real-World Use Case

Durga Antivirus Pro dashboard loaded Bootstrap CSS and jQuery as render-blocking resources. After inlining the critical CSS and deferring jQuery, First Contentful Paint dropped from 2.8 seconds to 1.1 seconds. The security dashboard became usable much faster for administrators.

Prerequisites

You should understand Critical Rendering Path concepts and how the CSS Object Model is constructed. Familiarity with JavaScript script loading behavior is necessary.

Step-by-Step Tutorial

Step 1: Identify Render-Blocking Resources

Open Chrome DevTools, run a Lighthouse report, and check the Opportunities section. Lighthouse lists all render-blocking resources with estimated time savings.

# Lighthouse CLI to find blocking resources
npx lighthouse https://example.com --output json --quiet \
  | python3 -c "
import sys, json
d = json.load(sys.stdin)
items = d['audits']['render-blocking-resources']['details']['items']
for i in items:
    print(f\"{i['url']} - {i['wastedMs']}ms\")
"

Expected output: A list of URLs with estimated savings. For example:

  • https://example.com/style.css - 380ms
  • https://example.com/app.js - 260ms

Step 2: Understand What Makes a Resource Blocking

A CSS or JavaScript resource is render-blocking when:

  • CSS: Any <link rel="stylesheet"> in the <head> blocks rendering. The browser pauses DOM construction until the CSSOM is built.
  • JavaScript: Any <script> tag without defer or async blocks DOM construction if placed before the CSSOM is ready.
<!-- Blocking: parser halts until styles.css is downloaded and parsed -->
<link rel="stylesheet" href="styles.css">

<!-- Blocking: parser halts until app.js is downloaded and executed -->
<script src="app.js"></script>

<!-- Non-blocking: deferred execution -->
<script src="app.js" defer></script>

<!-- Non-blocking: async execution -->
<script src="analytics.js" async></script>

Step 3: Eliminate Blocking CSS

There are two complementary approaches:

Approach A: Inline Critical CSS

Extract the CSS needed for above-the-fold content and inline it in the <head>. Load the remaining CSS asynchronously.

<head>
  <!-- Inlined critical CSS - renders immediately -->
  <style>
    header { background: #1a1a2e; padding: 1rem; }
    .hero { font-size: 2rem; color: #e94560; }
    /* Only above-the-fold styles */
  </style>
  
  <!-- Non-critical CSS loaded asynchronously via preload -->
  <link rel="preload" href="/styles/main.css" as="style"
        onload="this.onload=null;this.rel='stylesheet'">
  <noscript><link rel="stylesheet" href="/styles/main.css"></noscript>
</head>

Approach B: Split CSS by Media Type

Use media queries to mark CSS as non-blocking.

<!-- Blocking on all devices -->
<link rel="stylesheet" href="main.css">

<!-- Non-blocking: only blocks printing -->
<link rel="stylesheet" href="print.css" media="print">

<!-- Non-blocking: only loads when orientation matches -->
<link rel="stylesheet" href="portrait.css" media="orientation:portrait">

Step 4: Eliminate Blocking JavaScript

Apply the correct script loading attribute based on when the script is needed.

<!-- Blocking: parser halts -->
<script src="app.js"></script>

<!-- Deferred: downloads during parsing, executes after parsing (preserves order) -->
<script src="app.js" defer></script>

<!-- Async: downloads during parsing, executes immediately after download (no order) -->
<script src="analytics.js" async></script>

For scripts that must load before the page renders (critical rendering scripts), inline them directly.

<head>
  <script>
    // Critical rendering logic - inlined to avoid a blocking request
    function loadTheme() {
      const theme = localStorage.getItem('theme') || 'dark';
      document.documentElement.setAttribute('data-theme', theme);
    }
    loadTheme();
  </script>
</head>

Step 5: Use the Script Loading Decision Tree

Use this decision tree for each script on your page:

  1. Is this script needed before rendering? → Inline it
  2. Is this script independent with no order requirements? → Use async
  3. Does this script need to preserve execution order? → Use defer
  4. Is this script only needed on user interaction? → Use dynamic import()
// Dynamic import for interaction-triggered scripts
document.getElementById('load-chart').addEventListener('click', async () => {
  const { Chart } = await import('chart.js');
  // Initialize chart...
});

Step 6: Analyze the Critical Path Depth

Count the number of round trips in your critical path. Each blocking resource adds one round trip.

// Calculate critical path depth
const blockingResources = [
  'HTML document (1 RTT)',
  'critical.css (1 RTT - if not inlined)',
  'critical.js (1 RTT - if not inlined)'
];
console.log(`Critical path depth: ${blockingResources.length} round trips`);

Goal: Reduce the critical path to 1 round trip (the HTML itself). Everything else should load in parallel.

Step 7: Automate Blocking Resource Detection

Add a check to your CI pipeline to flag new render-blocking resources.

// Custom script to check for blocking resources
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.coverage.startCSSCoverage();
  await page.goto('https://example.com');
  const cssCoverage = await page.coverage.stopCSSCoverage();
  
  cssCoverage.forEach(entry => {
    const usedBytes = entry.text.length * 
      entry.ranges.reduce((sum, r) => sum + r.end - r.start, 0) / entry.text.length;
    const usedPercent = (usedBytes / entry.text.length * 100).toFixed(1);
    console.log(`${entry.url}: ${usedPercent}% used`);
    if (usedPercent < 50) {
      console.log(`  WARNING: Less than 50% of this CSS is used above the fold`);
    }
  });
  
  await browser.close();
})();

Step 8: Measure the Improvement

Compare before and after metrics using the Performance API.

// Measure first paint time
window.addEventListener('load', () => {
  const paintEntries = performance.getEntriesByType('paint');
  paintEntries.forEach(entry => {
    console.log(`${entry.name}: ${entry.startTime}ms`);
  });
  // Expected output:
  // first-paint: 210ms (before: 850ms)
  // first-contentful-paint: 230ms (before: 920ms)
});

Learning Path

flowchart LR
  A[Critical Rendering Path] --> B[Render-Blocking Resources]
  B --> C[Preload, Prefetch, Preconnect]
  B --> D[CSS Performance]
  C --> E[Performance Budgets]
  
  style B fill:#4f46e5,color:#fff
  style A fill:#6366f1,color:#fff
  style C fill:#6366f1,color:#fff

Common Errors

  1. Treating all CSS as blocking: Not all CSS needs to be blocking. Use media attributes to make print or orientation-specific CSS non-blocking.

  2. Using async for all scripts: Async scripts execute as soon as they download, which can happen in the middle of DOM construction. Use async only for independent scripts like analytics.

  3. Inlining large CSS blocks: Inlining the entire stylesheet (e.g., 50KB of CSS) delays HTML parsing. Only inline critical above-the-fold CSS, which is typically 2-8KB.

  4. Putting scripts in the head without defer: Scripts in the <head> without defer or async are the worst offenders. Move scripts to the end of <body> or add defer.

  5. Not inlining critical JavaScript: JavaScript that affects the initial render (theme detection, redirects, A/B testing) should be inlined to avoid blocking requests.

  6. Ignoring inline event handlers: Inline event handlers like onclick="handler()" are not blocking themselves, but they prevent the browser from caching the handling logic separately.

Practice Questions

  1. What determines whether a CSS resource is render-blocking?
  2. What is the difference between async and defer for script loading?
  3. Why does inlining critical CSS improve render performance?
  4. How can media attributes make CSS non-blocking?
  5. What is the critical path depth and why does it matter?

Answers: 1. A CSS resource blocks rendering if it is a <link rel="stylesheet"> in the <head> without a media attribute that evaluates to false. 2. async executes as soon as the script downloads; defer executes after HTML parsing completes in document order. 3. Inlined CSS requires no network request and is available immediately when the parser encounters it. 4. By adding media="print" or media="orientation:portrait", the browser knows the CSS is not needed for the current state and makes it low-priority or non-blocking. 5. The number of sequential network requests needed before the first paint; each additional round trip increases time to first paint.

Challenge

Audit a production page and list all render-blocking resources. Calculate the total potential time savings. Apply the following optimizations: inline critical CSS, defer all non-critical JavaScript, add media queries to theme CSS files, and convert async-incompatible scripts to defer. Run Lighthouse before and after to measure the FCP and LCP improvement.

FAQ

Do preloaded CSS files block rendering?

Preloaded CSS (<link rel="preload" ... as="style">) does not block rendering by itself. The browser starts the download early but does not apply the styles until the preload is promoted to a stylesheet via onload.

Can I completely eliminate all render-blocking resources?

The HTML itself is technically blocking. For CSS, you can eliminate all external blocking CSS by inlining everything, but this increases HTML size. Strike a balance by inlining critical CSS and loading the rest async.

Does HTTP/2 multiplexing eliminate the need to inline CSS?

No. Even with HTTP/2, each request adds at least one round trip latency. Inlining eliminates the round trip entirely for critical CSS, which is still beneficial.

How does third-party CSS affect rendering?

Third-party CSS (widgets, fonts, embeds) can block rendering if loaded synchronously in the head. Load third-party CSS async when possible, or use preconnect to reduce connection latency.

What tools does DodaTech use for render-blocking analysis?

DodaTech uses Lighthouse CLI for automated detection, Chrome DevTools Coverage tab for manual analysis, and a custom Puppeteer script in CI that flags new render-blocking resources on pull requests.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro