Back to Guides

Eliminate Render-Blocking Resources

Master techniques to identify and eliminate render-blocking CSS and JavaScript. Improve First Contentful Paint (FCP) and user-perceived performance.

What Are Render-Blocking Resources?

Render-blocking resources are CSS and JavaScript files that prevent the browser from displaying content until they're downloaded, parsed, and executed. They directly delay First Contentful Paint (FCP), making pages feel slow.

How Browsers Render Pages

  1. 1. Parse HTML → Build DOM tree
  2. 2. Encounter CSS → Stop rendering, download and parse CSS
  3. 3. Encounter JavaScript → Stop parsing, download and execute JS
  4. 4. Complete CSSOM → Combine with DOM to create render tree
  5. 5. Paint → Display content on screen (FCP!)

Problem: Steps 2-3 block rendering. External CSS and JavaScript in <head> delay FCP by hundreds or thousands of milliseconds.

Identifying Render-Blocking Resources

Use Chrome DevTools and Lighthouse to identify which resources are blocking rendering.

Lighthouse Audit

Run Lighthouse and look for the "Eliminate render-blocking resources" opportunity. It will list specific CSS and JS files blocking FCP.

bash
# Run Lighthouse from command line
npx lighthouse https://yoursite.com --view
# Look for:
# ⚠️ Eliminate render-blocking resources
# • /css/styles.css (200ms potential savings)
# • /js/app.js (150ms potential savings)

Chrome DevTools Coverage

  1. 1. Open DevTools (F12) → Coverage tab → Click record
  2. 2. Reload page and let it fully load
  3. 3. Sort by "Unused Bytes" to find bloated render-blocking files
  4. 4. Red bars show unused code - candidates for code splitting

Eliminating Render-Blocking CSS

CSS is render-blocking by design (browsers need styles before painting). The solution: inline critical CSS and defer non-critical CSS.

1. Inline Critical CSS

Extract CSS for above-the-fold content and inline it in the <head>. This allows the browser to paint immediately without waiting for external stylesheets.

html
<!DOCTYPE html>
<html>
<head>
<!-- Inline critical CSS for above-the-fold content -->
<style>
/* Critical styles for header, hero, layout */
body { margin: 0; font-family: sans-serif; }
.header { background: #fff; padding: 1rem; }
.hero { min-height: 400px; background: #f0f0f0; }
</style>
<!-- Defer non-critical CSS -->
<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>
<body>
<!-- Content -->
</body>
</html>

2. Use Critical CSS Extraction Tools

bash
# Install critical (automatically extracts critical CSS)
npm install --save-dev critical
# Extract critical CSS during build
const critical = require('critical');
critical.generate({
src: 'index.html',
target: {
html: 'index-critical.html',
css: 'critical.css'
},
width: 1300,
height: 900,
inline: true // Inline critical CSS
});

3. Media Queries for Non-Critical CSS

Use media queries to prevent CSS from blocking render for content that's not immediately visible.

html
<!-- Non-blocking: CSS for print -->
<link rel="stylesheet" href="/print.css" media="print" />
<!-- Non-blocking: CSS that only applies on large screens -->
<link rel="stylesheet" href="/desktop-only.css" media="(min-width: 1200px)" />
<!-- Trick: Load with print media, then switch to all -->
<link rel="stylesheet" href="/styles.css" media="print" onload="this.media='all'" />

4. Remove Unused CSS

bash
# Use PurgeCSS to remove unused styles
npm install --save-dev @fullhuman/postcss-purgecss
# postcss.config.js
module.exports = {
plugins: [
require('@fullhuman/postcss-purgecss')({
content: ['./**/*.html', './**/*.jsx', './**/*.tsx'],
defaultExtractor: content => content.match(/[\w-/:]+(?<!:)/g) || []
})
]
}

Eliminating Render-Blocking JavaScript

JavaScript in the <head> blocks HTML parsing. Use async, defer, or move scripts to the end of <body>.

1. Use async and defer Attributes

html
<!-- Bad: Blocks HTML parsing -->
<script src="/app.js"></script>
<!-- Good: defer - Download in parallel, execute after HTML parsed -->
<script src="/app.js" defer></script>
<!-- Good: async - Download and execute ASAP (for independent scripts) -->
<script src="/analytics.js" async></script>
<!-- Best practice: Use defer for most scripts -->
<script src="/main.js" defer></script>
<script src="/vendor.js" defer></script>

async vs defer

AttributeDownloadExecutionUse Case
deferParallelAfter HTML parsingMost scripts (maintains order)
asyncParallelASAP (order not guaranteed)Independent scripts (analytics)
(none)Blocks parsingImmediatelyCritical inline scripts only

2. Inline Critical JavaScript

For truly critical JS (feature detection, font loading), inline it directly in HTML to avoid network requests.

html
<head>
<!-- Inline critical JS (keep under 1KB) -->
<script>
// Detect WebP support
function supportsWebP() {
const elem = document.createElement('canvas');
return elem.toDataURL('image/webp').indexOf('data:image/webp') === 0;
}
if (supportsWebP()) {
document.documentElement.classList.add('webp');
}
</script>
</head>

3. Code Splitting

Split JavaScript into smaller chunks loaded on-demand. This reduces the initial bundle size and eliminates render-blocking for unused code.

javascript
// Dynamic imports for code splitting (Webpack/Vite)
import('./heavy-module.js').then(module => {
module.init();
});
// React lazy loading
import { lazy, Suspense } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</Suspense>
);
}
// Next.js automatic code splitting
// Each page is automatically split
// pages/home.js → home.js bundle
// pages/about.js → about.js bundle

Optimizing Web Fonts (Common Render Blocker)

Web fonts can block rendering if not optimized. Use font-display and preloading to prevent FOIT (Flash of Invisible Text).

1. Use font-display: swap

css
/* Show fallback font immediately, swap when custom font loads */
@font-face {
font-family: 'CustomFont';
src: url('/fonts/custom.woff2') format('woff2');
font-display: swap; /* Prevents invisible text */
}

2. Preload Critical Fonts

html
<!-- Preload critical fonts to start download early -->
<link rel="preload" href="/fonts/roboto-regular.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/fonts/roboto-bold.woff2" as="font" type="font/woff2" crossorigin />
<!-- Note: Only preload 1-2 most critical fonts -->

3. Self-Host Google Fonts

bash
# Use fontsource for self-hosted Google Fonts
npm install @fontsource/roboto
# Import only needed weights
import '@fontsource/roboto/400.css';
import '@fontsource/roboto/700.css';
html
<!-- Or optimize Google Fonts link -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" />

Framework-Specific Solutions

Next.js Optimization

javascript
// next.config.js - Automatic CSS/JS optimization
module.exports = {
// CSS optimization built-in
// Automatic code splitting by route
// Optimize fonts
optimizeFonts: true,
// Enable SWC minification (faster than Terser)
swcMinify: true,
};
// Use next/script for third-party scripts
import Script from 'next/script';
export default function Page() {
return (
<>
{/* Defer non-critical scripts */}
<Script src="/analytics.js" strategy="lazyOnload" />
</>
);
}

React/Vite Optimization

javascript
// vite.config.js
export default {
build: {
// Code splitting configuration
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
utils: ['lodash', 'date-fns']
}
}
},
// CSS code splitting
cssCodeSplit: true,
// Minification
minify: 'terser'
}
};

WordPress Optimization

javascript
// functions.php - Defer non-critical scripts
function defer_scripts($tag, $handle, $src) {
// Don't defer jQuery (many plugins depend on it)
if ('jquery' === $handle) {
return $tag;
}
// Defer other scripts
return str_replace(' src', ' defer src', $tag);
}
add_filter('script_loader_tag', 'defer_scripts', 10, 3);
// Use plugins: Autoptimize, WP Rocket for CSS/JS optimization

Testing Your Optimizations

Measure Impact on FCP

  1. 1. Before optimization: Run Lighthouse, note FCP time and "Eliminate render-blocking resources" opportunity
  2. 2. Apply optimizations: Inline critical CSS, defer JavaScript, optimize fonts
  3. 3. After optimization: Re-run Lighthouse, compare FCP improvement
  4. 4. Goal: FCP < 1.8s (good), no render-blocking resource warnings

Chrome DevTools Performance Timeline

  1. 1. Open DevTools → Performance tab
  2. 2. Record page load with throttling (Slow 3G, CPU 4x slowdown)
  3. 3. Look for long "Evaluate Script" and "Parse Stylesheet" tasks
  4. 4. These are render-blocking - optimize them first

Render-Blocking Optimization Checklist

Critical CSS inlined in <head> (under 14KB)
Non-critical CSS deferred with preload + media trick
Unused CSS removed with PurgeCSS or similar
All JavaScript uses defer attribute (or async for independent scripts)
JavaScript code-split by route/component
Web fonts use font-display: swap
Critical fonts preloaded (max 2 fonts)
Third-party scripts deferred or loaded asynchronously
FCP < 1.8s in Lighthouse (mobile)
Lighthouse shows no render-blocking resource warnings

Identify Your Render-Blocking Resources

Get a detailed analysis of which CSS and JavaScript files are blocking your page rendering, plus AI-powered recommendations to fix them.

Analyze Render-Blocking Resources