Understanding Render-Blocking Resources
Render-blocking CSS and JavaScript delay your page from showing any content. Learn how to identify and eliminate render-blocking resources.
"Eliminate render-blocking resources" is one of the most common Lighthouse recommendations. It's also one of the most impactful — fixing it can shave seconds off your First Contentful Paint.
What Are Render-Blocking Resources?
When the browser parses your HTML and encounters a <link> to a CSS file or a <script> tag, it stops rendering the page until that resource is downloaded and processed.
This means your users stare at a blank white screen while the browser fetches files it may not even need for the initial view.
CSS Is Always Render-Blocking
By default, ALL CSS is render-blocking. The browser won't paint a single pixel until it has downloaded and parsed every CSS file in the <head>:
<head>
<!-- Browser stops rendering until ALL of these load -->
<link rel="stylesheet" href="/reset.css">
<link rel="stylesheet" href="/layout.css">
<link rel="stylesheet" href="/components.css">
<link rel="stylesheet" href="/pages/home.css">
<link rel="stylesheet" href="/vendor/slider.css">
</head>
JavaScript Blocks by Default
Synchronous <script> tags block both HTML parsing AND rendering:
<head>
<!-- Blocks parsing until downloaded AND executed -->
<script src="/analytics.js"></script>
<script src="/vendor/jquery.js"></script>
<script src="/app.js"></script>
</head>
The Impact on Performance
For a typical page with 5 CSS files and 3 JavaScript files in the <head>:
- Browser starts parsing HTML
- Encounters first CSS file → stops, fetches it (200ms)
- Encounters second CSS file → fetches it (150ms)
- ... continues for all CSS files
- Encounters first JS file → stops parsing, fetches and executes (300ms)
- ... continues for all JS files
- Finally starts rendering — 1-3 seconds after the HTML arrived
On a slow 4G connection, each file fetch might take 200-500ms. Five render-blocking resources could delay FCP by 2+ seconds.
How to Fix Render-Blocking CSS
1. Inline Critical CSS
Extract the CSS needed for above-the-fold content and put it directly in the HTML:
<head>
<style>
/* Only styles needed for initial viewport */
.header { display: flex; background: #1a1a2e; }
.hero { min-height: 80vh; }
.cta-button { background: #e94560; padding: 12px 24px; }
</style>
</head>
Tools to automate this:
- critical (npm package)
- Critters (webpack plugin)
- PurgeCSS (removes unused CSS)
2. Load Non-Critical CSS Asynchronously
<!-- Preload the stylesheet -->
<link rel="preload" href="/styles.css" as="style"
onload="this.onload=null;this.rel='stylesheet'">
<!-- Fallback for no-JS -->
<noscript><link rel="stylesheet" href="/styles.css"></noscript>
3. Use Media Queries to Defer
CSS with specific media queries only blocks rendering for that media:
<!-- Only blocks on screens wider than 768px -->
<link rel="stylesheet" href="/desktop.css" media="(min-width: 768px)">
<!-- Only blocks when printing -->
<link rel="stylesheet" href="/print.css" media="print">
4. Combine and Minimize CSS Files
Fewer files = fewer round trips:
<!-- Before: 5 requests -->
<link rel="stylesheet" href="/reset.css">
<link rel="stylesheet" href="/layout.css">
<link rel="stylesheet" href="/components.css">
<link rel="stylesheet" href="/pages.css">
<link rel="stylesheet" href="/vendor.css">
<!-- After: 1 request -->
<link rel="stylesheet" href="/styles.min.css">
How to Fix Render-Blocking JavaScript
1. Use defer
Scripts with defer download in parallel but execute after HTML parsing:
<script src="/app.js" defer></script>
2. Use async
Scripts with async download in parallel and execute as soon as they arrive (use for independent scripts like analytics):
<script src="/analytics.js" async></script>
3. Move Scripts to Body End
If you can't use defer or async, move scripts to the end of <body>:
<body>
<!-- Page content renders first -->
<script src="/app.js"></script>
</body>
4. Dynamic Import
Load JavaScript only when needed:
// Load chat widget only when user scrolls to footer
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
import('./chat-widget.js');
observer.disconnect();
}
});
observer.observe(document.querySelector('footer'));
defer vs. async vs. No Attribute
| Attribute | Download | Execute | Use Case |
|---|---|---|---|
| None | Blocks parsing | Blocks parsing | Never (avoid) |
async |
Parallel | Immediately when ready | Analytics, ads |
defer |
Parallel | After HTML parsing | App code, UI logic |
Measuring Render-Blocking Impact
Lighthouse Audit
Run Lighthouse and look for "Eliminate render-blocking resources." It lists each resource with its estimated time impact.
Chrome DevTools Coverage Tab
- Open DevTools → More tools → Coverage
- Reload the page
- See exactly how much of each CSS/JS file is actually used
You might find that 70% of your CSS is unused on the current page.
The Result
Properly handling render-blocking resources typically improves FCP by 0.5-2 seconds on mobile connections. That's the difference between a user seeing content and a user seeing a blank page.
Ready to stop wasting ad spend?
Track your landing page performance, monitor Core Web Vitals, and calculate exactly how much slow pages cost you.
Start Free — No Credit Card