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. Parse HTML → Build DOM tree
- 2. Encounter CSS → Stop rendering, download and parse CSS
- 3. Encounter JavaScript → Stop parsing, download and execute JS
- 4. Complete CSSOM → Combine with DOM to create render tree
- 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.
# Run Lighthouse from command linenpx 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. Open DevTools (F12) → Coverage tab → Click record
- 2. Reload page and let it fully load
- 3. Sort by "Unused Bytes" to find bloated render-blocking files
- 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.
<!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
# Install critical (automatically extracts critical CSS)npm install --save-dev critical
# Extract critical CSS during buildconst 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.
<!-- 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
# Use PurgeCSS to remove unused stylesnpm install --save-dev @fullhuman/postcss-purgecss
# postcss.config.jsmodule.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
<!-- 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
| Attribute | Download | Execution | Use Case |
|---|---|---|---|
| defer | Parallel | After HTML parsing | Most scripts (maintains order) |
| async | Parallel | ASAP (order not guaranteed) | Independent scripts (analytics) |
| (none) | Blocks parsing | Immediately | Critical inline scripts only |
2. Inline Critical JavaScript
For truly critical JS (feature detection, font loading), inline it directly in HTML to avoid network requests.
<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.
// Dynamic imports for code splitting (Webpack/Vite)import('./heavy-module.js').then(module => { module.init();});
// React lazy loadingimport { 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 bundleOptimizing 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
/* 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
<!-- 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
# Use fontsource for self-hosted Google Fontsnpm install @fontsource/roboto
# Import only needed weightsimport '@fontsource/roboto/400.css';import '@fontsource/roboto/700.css';<!-- 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
// next.config.js - Automatic CSS/JS optimizationmodule.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 scriptsimport Script from 'next/script';
export default function Page() { return ( <> {/* Defer non-critical scripts */} <Script src="/analytics.js" strategy="lazyOnload" /> </> );}React/Vite Optimization
// vite.config.jsexport 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
// functions.php - Defer non-critical scriptsfunction 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 optimizationTesting Your Optimizations
Measure Impact on FCP
- 1. Before optimization: Run Lighthouse, note FCP time and "Eliminate render-blocking resources" opportunity
- 2. Apply optimizations: Inline critical CSS, defer JavaScript, optimize fonts
- 3. After optimization: Re-run Lighthouse, compare FCP improvement
- 4. Goal: FCP < 1.8s (good), no render-blocking resource warnings
Chrome DevTools Performance Timeline
- 1. Open DevTools → Performance tab
- 2. Record page load with throttling (Slow 3G, CPU 4x slowdown)
- 3. Look for long "Evaluate Script" and "Parse Stylesheet" tasks
- 4. These are render-blocking - optimize them first
Render-Blocking Optimization Checklist
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