Back to Guides

Render-Blocking Resources: How to Fix Them for Faster Page Loads

Render-blocking resources are the silent killers of page speed. Your browser downloads HTML, starts building the page, then hits a <link> or <script> tag — and stops everything until that file is fetched and processed. The user sees a blank screen. Your Lighthouse score tanks. Google notices.

What Are Render-Blocking Resources?

When the browser parses HTML, two types of resources block rendering by default:

  1. CSS stylesheets — the browser won't paint anything until all CSS in <head> is downloaded and parsed (it needs the full CSSOM to render correctly)
  2. Synchronous JavaScript — a <script> tag without async or defer pauses HTML parsing entirely while the script downloads and executes

This means a 200KB CSS file on a slow connection can delay first paint by several seconds, even if the HTML itself is tiny. And a single blocking script from a third-party provider can hold your entire page hostage.

How to Identify Render-Blocking Resources

Run your URL through PageSpeed Analyzer and look for the "Eliminate render-blocking resources" audit. It lists every CSS and JS file that blocks first paint, along with the estimated time savings.

You can also check in Chrome DevTools:

  1. Open Performance tab, record a page load
  2. Look at the Network waterfall — any resource that extends the blue "DCL" (DOMContentLoaded) line is likely blocking
  3. The Coverage tab (Cmd+Shift+P → "Coverage") shows how much of each CSS/JS file is actually used on initial load

Fixing Render-Blocking CSS

1. Inline Critical CSS

Critical CSS is the minimal set of styles needed to render above-the-fold content. Inline it directly in <head> so the browser can paint immediately without waiting for external stylesheets:

html
<head>
<style>
/* Critical CSS — only what's needed for above-the-fold */
body { margin: 0; font-family: system-ui, sans-serif; }
.header { background: #1a1a2e; color: #fff; padding: 1rem; }
.hero { max-width: 800px; margin: 2rem auto; }
.hero h1 { font-size: 2.5rem; line-height: 1.2; }
</style>
</head>

2. Defer Non-Critical CSS

Load the full stylesheet asynchronously using the media attribute trick:

html
<!-- Load full stylesheet without blocking render -->
<link rel="stylesheet"
href="/css/main.css"
media="print"
onload="this.media='all'">
<!-- Fallback for no-JS -->
<noscript>
<link rel="stylesheet" href="/css/main.css">
</noscript>

Setting media="print" tells the browser the stylesheet isn't needed for screen rendering, so it downloads it without blocking. Once loaded, onload switches it to media="all" and styles apply.

3. Split CSS by Route

If you're shipping one giant main.css for your entire site, most of it is unused on any given page. Split it:

html
<!-- Base styles needed everywhere -->
<link rel="stylesheet" href="/css/base.css">
<!-- Page-specific styles loaded only where needed -->
<link rel="stylesheet" href="/css/blog-post.css">

Most bundlers (Vite, Webpack, Parcel) handle CSS code splitting automatically when you use dynamic imports in your JavaScript.

Fixing Render-Blocking JavaScript

1. Use defer for App Scripts

defer downloads the script in parallel with HTML parsing and executes it after the document is parsed — in the order scripts appear:

html
<!-- Downloads in parallel, executes after HTML parsing, maintains order -->
<script src="/js/app.js" defer></script>
<script src="/js/analytics.js" defer></script>

Use defer for your application code that depends on the DOM or needs to run in a specific order.

2. Use async for Independent Scripts

async downloads in parallel and executes as soon as the download finishes — regardless of HTML parsing or other scripts:

html
<!-- Downloads in parallel, executes immediately when ready, no ordering -->
<script src="/js/chat-widget.js" async></script>

Use async for scripts that are independent and don't rely on DOM state or other scripts. Analytics and third-party widgets are good candidates. See the third-party scripts guide for more on managing these.

3. Use type="module" for Modern Code

ES modules are deferred by default:

html
<!-- Deferred automatically, supports import/export -->
<script type="module" src="/js/app.mjs"></script>

Quick Reference: Script Loading Behavior

AttributeDownloadsExecutesBlocks ParsingOrder
(none)ImmediatelyImmediatelyYesYes
asyncIn parallelWhen readyBrieflyNo
deferIn parallelAfter parsingNoYes
type="module"In parallelAfter parsingNoYes

Preload Hints

Use <link rel="preload"> to tell the browser about critical resources it won't discover until later in the parsing process:

html
<head>
<!-- Preload a font the CSS will request -->
<link rel="preload" href="/fonts/inter.woff2"
as="font" type="font/woff2" crossorigin>
<!-- Preload a hero image referenced in CSS -->
<link rel="preload" href="/img/hero.webp" as="image">
<!-- Preload a critical script -->
<link rel="preload" href="/js/app.js" as="script">
</head>

Preloading moves resource discovery earlier in the page load timeline. This is especially impactful for fonts (which aren't discovered until CSS is parsed) and for resources that affect Core Web Vitals like LCP images.

Don't overuse preload. Every preloaded resource competes for bandwidth. Limit it to 2–4 truly critical resources.

Automate This in Your Build Pipeline

Manual critical CSS extraction and script tagging doesn't scale. Automate it:

  • Critical CSS extraction: Tools like critical (npm package) extract above-the-fold CSS at build time
  • CSS/JS code splitting: Vite and Webpack handle this natively with dynamic imports
  • Script attribute injection: html-webpack-plugin's scriptLoading: 'defer' option auto-adds defer to all scripts
  • Font preloading: Bundler plugins can auto-generate <link rel="preload"> for font files

If your deployment process includes a build step, these optimizations run automatically on every deploy. Build pipelines let you run npm run build (or equivalent) on the server before files go live — so your production assets are always optimized, even if a developer forgets locally.

For JavaScript-heavy sites, also review the JavaScript optimization guide for techniques like tree shaking and lazy loading that complement render-blocking fixes.

Checklist

  • Critical CSS is inlined in <head> for above-the-fold content
  • Non-critical CSS is loaded asynchronously (media="print" + onload)
  • CSS is split per page or route — no monolithic stylesheets
  • All <script> tags use defer, async, or type="module"
  • No synchronous third-party scripts in <head>
  • Fonts and LCP images use <link rel="preload">
  • Build pipeline handles critical CSS extraction automatically
  • PageSpeed Analyzer shows no render-blocking audit failures

Test Your Render-Blocking Resources

Run your URL through PageSpeed Analyzer — the render-blocking resources audit should disappear after applying these fixes.

More guides: Third-Party Scripts · JavaScript Optimization · Core Web Vitals · Image Optimization

This content is provided by PageSpeed Analyzer by DeployHQ.