Fix Render-Blocking Resources for Better LCP

Eliminate render-blocking CSS and JavaScript to improve LCP. Learn async/defer scripts, critical CSS extraction, and font loading strategies.
Harlan WiltonHarlan Wilton8 min read Published

Render-blocking resources prevent the browser from painting anything—even when your LCP element is fully downloaded and ready. The median website loads 7 render-blocking resources totaling 100KB+, adding 500-1500ms to LCP on typical mobile connections.

LCPLargest Contentful Paint CWV
25% weight
Good ≤2.5sPoor >4.0s

What's the Problem?

Browsers follow a strict rule: no pixels appear on screen until all render-blocking resources finish loading. CSS is render-blocking by default because the browser needs styles to know what to paint. Synchronous JavaScript is parser-blocking, which halts HTML processing until the script executes.

The impact is severe. Your LCP image might download in 200ms, but if 300KB of CSS is still loading, users see nothing. The browser has the content; it refuses to display it. This creates an invisible delay that compounds with every additional blocking resource in the critical path.

Lighthouse flags this as "Eliminate render-blocking resources" and calculates the potential time savings. The audit identifies CSS files loaded in the <head> without media attributes and JavaScript files without async or defer. Each blocking resource in the chain extends the time before First Contentful Paint and LCP.

How to Identify This Issue

Chrome DevTools

  1. Open DevTools (F12) and go to the Performance tab
  2. Click Record, then reload the page
  3. Look at the Main thread flame chart
  4. Long pink/purple bars before "First Paint" indicate blocking resources
  5. The Network waterfall shows which resources block rendering with a vertical blue line

Command Line

npx lighthouse https://your-site.com --only-audits=render-blocking-resources --output=json | jq '.audits["render-blocking-resources"]'

curl -sI https://your-site.com/styles.css | grep -i content-length

Lighthouse Indicators

Look for "Eliminate render-blocking resources" under Opportunities. Lighthouse shows:

  • Each blocking URL
  • Transfer size in bytes
  • Wasted time in milliseconds

The audit calculates savings based on how much faster FCP and LCP could occur if resources loaded asynchronously.

The Fix

Primary: Async/Defer Scripts

Never load JavaScript synchronously in the <head> unless absolutely necessary.

<!-- Before: blocking -->
<head>
  <script src="analytics.js"></script>
  <script src="app.js"></script>
</head>

<!-- After: non-blocking -->
<head>
  <script src="analytics.js" async></script>
  <script src="app.js" defer></script>
</head>

Choosing between async and defer:

<!-- async: downloads in parallel, executes immediately when ready -->
<!-- Use for independent scripts like analytics, ads -->
<script src="analytics.js" async></script>

<!-- defer: downloads in parallel, executes after HTML parsing, maintains order -->
<!-- Use for app code that depends on DOM or other scripts -->
<script src="vendor.js" defer></script>
<script src="app.js" defer></script>

For inline scripts that can run later:

<script>
  // Defer execution until after DOMContentLoaded
  document.addEventListener('DOMContentLoaded', () => {
    initializeNonCriticalFeatures()
  })
</script>

Secondary: Critical CSS Extraction

Extract only the CSS needed for above-the-fold content and inline it directly in the HTML.

<head>
  <!-- Critical CSS inlined -->
  <style>
    /* Only styles for above-the-fold content */
    :root { --primary: #0066cc; }
    body { margin: 0; font-family: system-ui; }
    .header { height: 60px; background: var(--primary); }
    .hero { padding: 2rem; }
    .hero-image { width: 100%; height: auto; }
  </style>

  <!-- Full CSS loaded asynchronously -->
  <link rel="preload" href="styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
  <noscript><link rel="stylesheet" href="styles.css"></noscript>
</head>

Automate critical CSS extraction:

npx critical https://your-site.com --inline --minify > index-critical.html

npm install critters-webpack-plugin --save-dev
// webpack.config.js
const Critters = require('critters-webpack-plugin')

module.exports = {
  plugins: [
    new Critters({
      preload: 'swap',
      inlineThreshold: 0,
    }),
  ],
}

Tertiary: Font Loading Strategy

Web fonts can block text rendering for up to 3 seconds on slow connections. Use font-display: swap to show fallback text immediately.

@font-face {
  font-family: 'Custom Font';
  src: url('/fonts/custom.woff2') format('woff2');
  font-display: swap;
}

Preload critical fonts:

<head>
  <link rel="preload" href="/fonts/custom.woff2" as="font" type="font/woff2" crossorigin>
</head>

For Google Fonts, add the display=swap parameter:

<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">

Bonus: Split Large CSS Files

Break monolithic CSS into route-specific chunks:

<!-- Load base styles everywhere -->
<link rel="stylesheet" href="base.css">

<!-- Load page-specific styles only where needed -->
<link rel="stylesheet" href="product-page.css" media="print" onload="this.media='all'">
// Dynamic CSS loading in JavaScript
document.getElementById('modal-trigger').addEventListener('click', () => {
  import('./modal.css')
  showModal()
})

Why This Works

Eliminating render-blocking resources allows the browser to start painting immediately after critical CSS is processed. The browser no longer waits for external stylesheets or JavaScript before displaying content. With async/defer scripts, JavaScript downloads in parallel without blocking HTML parsing. With inlined critical CSS, the browser has everything it needs to paint above-the-fold content in the initial HTML response. This removes artificial delays between resource availability and visual rendering.

Framework-Specific Solutions

Next.js: The framework automatically code-splits JavaScript by route. For CSS, use CSS Modules or styled-jsx for component-scoped styles. Enable experimental optimizeCss in next.config.js for automatic critical CSS extraction.
// next.config.js
module.exports = {
  experimental: {
    optimizeCss: true,
  },
}
Nuxt: Enable inline SSR styles for automatic critical CSS. Use component-level styles with <style scoped>. Configure font loading with @nuxt/fonts for optimal font-display settings.
// nuxt.config.ts
export default defineNuxtConfig({
  experimental: {
    inlineSSRStyles: true,
  },
  fonts: {
    defaults: {
      display: 'swap',
    },
  },
})

Verify the Fix

After implementing changes:

  1. Run Lighthouse and check that "Eliminate render-blocking resources" shows 0 items or passes
  2. Open Performance tab—First Paint should occur earlier in the timeline
  3. Verify no layout shift from font loading (check CLS score)
  4. Test with network throttling (Slow 3G) to see real-world impact
  5. Compare before/after screenshots at key time intervals using WebPageTest filmstrip

Expected improvement: Removing 200KB of render-blocking CSS and deferring 100KB of JavaScript typically saves 300-800ms on LCP for 4G mobile connections.

Common Mistakes

Inlining too much CSS: Critical CSS should be 14KB or less (fits in first TCP roundtrip). Inline only above-the-fold styles; load the rest asynchronously. Excessive inline CSS increases HTML size and hurts TTFB.

Breaking functionality with defer: Some legacy scripts expect synchronous execution and access to DOM elements that haven't parsed yet. Test thoroughly after adding defer. Use DOMContentLoaded listeners instead of relying on script position.

Missing fallback fonts: Using font-display: swap without specifying fallback fonts causes layout shift when custom fonts load. Define fallback font stacks that closely match your custom font metrics:

font-family: 'Custom Font', system-ui, -apple-system, sans-serif;

Render-blocking resources often combine with other LCP problems:

Test Your Entire Site

Different page templates have different blocking resources. Your blog might inline critical CSS perfectly while your product pages load three external stylesheets. Run a comprehensive audit to find render-blocking issues across every template.

Scan Your Site with Unlighthouse