To eliminate render-blocking resources, you take every stylesheet and script that the browser must download before it can draw anything, and either defer it, inline the small part the first screen needs, or remove it. Until those files arrive, the visitor looks at a white screen: a blink on office Wi-Fi, often two seconds or more on a mid-range phone on 4G. This guide explains what render blocking is, how to find the files responsible, the order to fix them in, and where the deeper guides on CSS, fonts, hints and JavaScript take over.
Quick answer
- Render-blocking resources are the stylesheets and ordinary
<script src>tags in<head>that the browser must fetch before the first paint. Images,asyncanddeferscripts are not. - Find them with the website speed test (the render-blocking finding lists each file and the estimated time saved) and the unused CSS and JavaScript checker (how much of each file the page actually uses).
- Scripts: add
deferto scripts in<head>, orasyncto independent ones such as analytics. - Stylesheets: inline the few kilobytes of CSS the first screen needs, load the rest without blocking, and give print or desktop-only CSS a
mediaattribute. - Dead weight: remove CSS and JavaScript that the page never uses, usually plugin files loaded on every page.
- Fonts and third-party origins: add
font-display: swap, self-host fonts where you can, andpreconnectto the two or three origins you keep. - Re-run the test. First Contentful Paint should drop by roughly the estimated savings.
What is render blocking?
Render blocking is anything that stops the browser from painting the page until it has finished. A browser reads your HTML from the top. When it meets certain files in <head>, it will not draw a single pixel until they have downloaded and been processed. Those files are render-blocking resources, and every one of them sits in front of the first thing a visitor sees.
Two kinds of file do this:
- Stylesheets block rendering. The browser will not paint until it has every stylesheet that applies to the current screen, because painting without them would show unstyled content that then jumps around. The HTML keeps loading in the background, but nothing appears.
- Scripts without
deferorasyncblock the parser. The browser stops reading the HTML, downloads the script, runs it, and only then continues. A script also waits for the stylesheets above it, because it might ask for a computed style. Three scripts from three different servers mean three connections to set up before the page can continue.
What does not block rendering:
| Resource | Render-blocking? |
|---|---|
<link rel="stylesheet"> in <head> | Yes |
A stylesheet with a media query that does not match, such as media="print" | No (downloaded at low priority) |
CSS pulled in with @import inside another stylesheet | Yes, and found late, which makes it worse |
<script src> in <head> without attributes | Yes |
<script src> with defer, async or type="module" | No |
Inline <style> and inline <script> | Not a separate request, but they still have to be parsed where they stand |
| Images, video, iframes | No (they can delay Largest Contentful Paint, which is a different problem) |
| Web font files | Not directly, but the text using them can stay invisible while they load |
Chromium also has a blocking="render" attribute that makes a file render-blocking on purpose; you will rarely want it.
"Render blocking requests" and "Eliminate render-blocking resources"
Google's tools have used two names for the same problem. Lighthouse and PageSpeed Insights called it "Eliminate render-blocking resources" for years. Since Lighthouse 13 (October 2025), the audit has moved into the "Render-blocking requests" insight, the same one Chrome DevTools shows in its Performance panel. Chrome's documentation describes it as requests that "prevented the first paint of the page". The files flagged and the fixes are the same under either name.
Why render-blocking resources matter
Every blocking file adds at least one round trip before the visitor sees anything. The result shows up in metrics you already track:
- First Contentful Paint (FCP) is the moment the first text or image appears. Every blocking file sits directly in front of it. Lighthouse counts an FCP up to 1.8 s on mobile as good.
- Largest Contentful Paint (LCP) is a Core Web Vital that Google uses in ranking, with 2.5 s as the "good" line. LCP cannot happen before FCP, so a blocked first paint drags the hero image back as well. When LCP is your worst number, fixing Largest Contentful Paint covers the image side of the same problem.
The fix is rarely "less CSS and JavaScript" in total. It is about when each file is needed. Most of the code in <head> is for things below the fold, for later interactions, or for other pages entirely.
Render blocking is one part of page speed. For the whole picture, from server response time to images and caching, start with the page speed guide; this guide covers the stretch between the first byte of HTML and the first paint, plus the JavaScript work that follows it.
How to find render-blocking resources
The speed test: which files block, and what it costs
The speed test runs Google PageSpeed Insights (Lighthouse) on a throttled mobile phone and on desktop. A resource counts as render-blocking when it is:
- a
<script src>in<head>withoutdefer,asyncortype="module", or - a
<link rel="stylesheet">without amediaquery that rules it out on the tested device, and withoutdisabled.
To estimate the savings, Lighthouse replays the page load in its network model with those files taken off the critical path and compares when the first paint would happen. It is a simulation on a slow phone, so read the number as a size, not a promise: a 1,200 ms estimate means the files matter a lot, a 90 ms estimate means there are better things to do first.

Open the technical detail to see the file list with each file's address. The request waterfall further down the Speed module colours render-blocking requests amber, so you can see them queued in front of everything else.
The unused code checker: how much of each file is needed
Knowing that a stylesheet blocks is half the answer. The other half is how much of it the page uses. The unused CSS and JavaScript checker loads the page in Chromium with the browser's own code coverage switched on, the same data as the Coverage tab in Chrome DevTools. It lists every script and stylesheet by unused bytes, first party and third party apart, next to Google's estimate of the time you would save.
A blocking stylesheet that is 90% unused is the best target on the page: the fix both unblocks the paint and shrinks the download. Coverage looks at one page load without clicks, so code for menus, forms or checkout shows as unused here even though it is needed later. Treat a big unused share as a sign to split the file by page, not as permission to delete it.
In your own browser
In Chrome DevTools, record a page load in the Performance panel and open the "Render-blocking requests" insight. For coverage, press Ctrl+Shift+P (Cmd+Shift+P on a Mac), run "Show Coverage" and reload.
How to eliminate render-blocking resources, step by step
Work in this order. Each step is safe on its own, and you can re-run the test after each one.
1. Defer the scripts in <head>
Most scripts do not need to run before the first paint: sliders, menus, cookie banners, chat widgets, analytics. Adding defer tells the browser to download them in parallel and run them after the HTML has been read, in the order they appear.
<!-- Before: blocks the parser until each file arrives and runs -->
<script src="/js/jquery.min.js"></script>
<script src="/js/slider.js"></script>
<!-- After: downloads in parallel, runs in order after parsing -->
<script src="/js/jquery.min.js" defer></script>
<script src="/js/slider.js" defer></script>On older templates where you cannot add attributes easily, moving <script> tags just before </body> has a similar effect: the content above them is parsed and painted first. defer in <head> is usually better, because the browser discovers the file earlier.
2. Choose between async and defer
The two attributes both stop a script from blocking, but they run it at different times, and picking the wrong one is the most common way a speed fix breaks a site.
defer | async | |
|---|---|---|
| Downloads | In parallel, without blocking | In parallel, without blocking |
| Runs | After the HTML is parsed, before DOMContentLoaded | As soon as it arrives, even mid-parse |
| Order | Keeps the order in the page | Whichever finishes first |
| Use for | Your own scripts, anything with dependencies | Independent scripts: analytics, tag managers, ads |
The rule of thumb: if a script needs another script (jQuery plugins need jQuery), use defer on both. If nothing depends on it and it depends on nothing, async is fine. type="module" scripts are deferred automatically. Inline scripts (without src) ignore both attributes, so a small inline script that calls a deferred library will fail; move that code into a file loaded with defer after the library. MDN's script element reference has the full rules.
The explainer on async vs defer covers the edge cases: when DOMContentLoaded fires relative to each, how modules and injected scripts behave, why document.write() stops working, what the "defer parsing of JavaScript" setting in plugins means, and how WordPress 6.3's loading strategies decide whether a script can really be deferred.
3. Inline the critical CSS, load the rest without blocking
"Critical CSS" is the small set of rules the first screen needs: header, navigation, hero, fonts, layout grid. Put those in a <style> block in <head> and load the full stylesheet so it does not block. Keep the inline part small; web.dev suggests staying under about 14 KB so it fits in the first round trip.
<head>
<style>
/* Critical rules for the first screen only */
body{margin:0;font-family:system-ui,sans-serif}
.site-header{display:flex;justify-content:space-between;padding:16px}
.hero{min-height:60vh;display:grid;place-items:center}
</style>
<!-- Full stylesheet: loads as print CSS (non-blocking), then switches to all -->
<link rel="stylesheet" href="/css/site.css" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="/css/site.css"></noscript>
</head>The media="print" trick works because the browser downloads print stylesheets at low priority and does not wait for them. When the file arrives, onload switches it to apply everywhere. The <noscript> line covers visitors without JavaScript. A variant uses rel="preload":
<link rel="preload" href="/css/site.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/css/site.css"></noscript>Both patterns use an inline onload handler. If your site sends a strict Content-Security-Policy without 'unsafe-inline' for scripts, the handler will not run and the stylesheet never applies; see Content-Security-Policy from report-only to enforced for nonces and hashes. web.dev's article on deferring non-critical CSS walks through the extraction.
Critical CSS is not always worth the upkeep. With HTTP/2 and one or two small stylesheets, preloading the main file is often enough, while a page builder shipping 200 KB of CSS may gain whole seconds. Critical CSS: is it still worth doing in 2026? has the decision table, the simpler things to try first, and how to regenerate the inline block so it does not go stale.
4. Split CSS by media query
A stylesheet whose media does not match the current device is downloaded but does not block rendering. Splitting print and wide-screen rules into their own files takes them off a phone's critical path:
<link rel="stylesheet" href="/css/base.css">
<link rel="stylesheet" href="/css/desktop.css" media="(min-width: 1024px)">
<link rel="stylesheet" href="/css/print.css" media="print">Also replace any @import inside a stylesheet with its own <link> tag. An imported file is only discovered after the first stylesheet has downloaded, so it adds a full extra round trip to the blocking chain.
5. Remove the CSS and JavaScript the page does not use
The fastest file is the one that is not sent. The usual sources are a theme or page builder that ships one large file for every possible layout, and plugins that load their assets on every page even though they are used on one (a contact form, a slider, a booking widget). Remove the files, or load them only on the pages that need them. In a build pipeline, PurgeCSS or your framework's CSS splitting does this automatically. The unused code checker shows which files to start with; the fix pages for unused CSS and unused JavaScript cover the common cases.
6. Fix the fonts
Web fonts are not render-blocking files themselves, but the stylesheet that declares them often is (Google Fonts' CSS is a stylesheet on another server), and without font-display the text stays invisible while the font loads. Self-hosting removes the extra connection and lets you set the behaviour yourself:
/* /css/site.css: self-hosted font files in /fonts/ */
@font-face {
font-family: "Inter";
src: url("/fonts/inter-var.woff2") format("woff2");
font-weight: 100 900;
font-display: swap;
}Preload the one or two font files the first screen uses:
<link rel="preload" href="/fonts/inter-var.woff2" as="font" type="font/woff2" crossorigin>If you keep Google Fonts, add &display=swap to the stylesheet URL and connect early to both origins it uses.
Fonts have their own trade-offs: which font-display value to pick, subsetting a 300 KB file down to 30 KB, and matching the fallback's metrics so the swap does not shift the layout. font-display: swap, preloading and self-hosting web fonts covers all of it, including when a system font stack is the better choice.
7. Preconnect to the third-party origins you keep
Every new server costs a DNS lookup, a TCP connection and a TLS handshake before the first byte, often 100-300 ms on mobile. preconnect starts that work immediately:
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>Limit it to two or three origins that are needed early; each preconnect holds a connection open whether it is used or not. preconnect is one of several resource hints, alongside preload, dns-prefetch, modulepreload, prefetch and the fetchpriority attribute. Preload, preconnect, prefetch: which resource hint when gives one hint per job with the exact syntax, and the mistakes that make hints slower than none.
After the first paint: JavaScript performance
Eliminating render-blocking resources gets something on screen sooner; it does not make the JavaScript cheaper. A deferred 600 KB bundle still has to be downloaded, parsed and run, and while it runs the page cannot respond to taps.
Long tasks and Total Blocking Time
A long task is any piece of main-thread work over 50 ms. Lighthouse adds up the part of each long task over 50 ms into Total Blocking Time, the most heavily weighted metric in its performance score, with under 200 ms counted as good. In the field, the same work shows up as a slow Interaction to Next Paint.
The fixes follow a similar order to this guide: remove scripts nobody needs, defer the rest, split bundles so each page loads only its own code, break long loops up with scheduler.yield(), and move heavy computation to a web worker. How to reduce Total Blocking Time walks through each one, with how to find the script behind every long task.
Third-party scripts
Tag managers, chat widgets, pixels, review carousels and video embeds are often the largest share of both render-blocking and main-thread work, and they arrive one at a time over years. Many of them are added to <head> without async because the vendor's snippet said so.
Third-party scripts: taming tag managers, chat widgets and ads shows how to inventory them, replace embeds with facades, gate tracking tags behind consent and hold the total to a budget. The third-party scripts fix page is the short version.
DOM size
A page with thousands of HTML elements makes every style recalculation and layout slower, and makes each script that walks the page slower too. Lighthouse warns above about 800 elements and fails above about 1,400. DOM size: why 3,000 nodes is a problem explains where the nodes come from, usually a page builder, a mega menu and hidden mobile copies of sections, and how to cut them.
Platform notes
WordPress
WordPress themes and plugins add files with wp_enqueue_style() and wp_enqueue_script(), and many enqueue on every page. That is why a site with dozens of plugins often has more than a dozen blocking files in <head>. Three kinds of tool deal with it from the admin, without code:
- Asset CleanUp and Perfmatters (Script Manager) let you switch off a plugin's CSS and JavaScript per page or per post type, for example the contact form plugin everywhere except the contact page.
- WP Rocket: File Optimization → "Optimize CSS delivery" with "Load CSS asynchronously" (generates critical CSS) or "Remove unused CSS"; "Load JavaScript deferred" and "Delay JavaScript execution" for scripts. LiteSpeed Cache has equivalents under Page Optimization. Our WP Rocket settings and LiteSpeed Cache settings guides give the values we recommend.
- Page builders such as Elementor have their own asset-loading settings that stop them shipping every widget's CSS on every page; see Elementor performance.
Turn on one option at a time and click through the site after each, especially menus, sliders, forms and the checkout. What each WordPress plugin costs your page shows how to find the heaviest plugins first.
The full walkthrough, eliminating render-blocking resources and unused CSS in WordPress, puts these tools in order: trace each file to its plugin by its handle, unload it where it is not used (with an asset manager or wp_dequeue_style()), switch on the builder's asset settings, and only then let one cache plugin defer, inline and remove unused CSS. It includes the code for unloading by handle and for per-block core styles on classic themes.
If you write the theme yourself, WordPress 6.3 and later can add defer for you:
// functions.php (child theme): load the theme script deferred, in the footer
add_action('wp_enqueue_scripts', function () {
wp_enqueue_script(
'theme-main',
get_stylesheet_directory_uri() . '/js/main.js',
[],
'1.0',
['strategy' => 'defer', 'in_footer' => true]
);
});Shopify
Check layout/theme.liquid (Online Store → Themes → Edit code). Themes built with script_tag output a plain blocking tag; write the tag yourself with defer:
{%- comment -%} Before: {{ 'vendor.js' | asset_url | script_tag }} {%- endcomment -%}
<script src="{{ 'vendor.js' | asset_url }}" defer></script>Apps are the other source. Current apps add their code as app embeds, which you switch off under Themes → Customize → App embeds. Older apps pasted code into theme.liquid or snippets, and that code often stays after the app is uninstalled; search the theme for the app's name. {{ content_for_header }} must stay; Shopify manages what it loads.
Static sites and frameworks
Modern frameworks emit module scripts, which are deferred, and split CSS per route. What still blocks is usually a global stylesheet, a font stylesheet from a CDN and third-party snippets in the head: keep global CSS small, self-host fonts, and load third parties through the framework's script component with a lazy strategy.
How to verify the fix
- Re-run the speed test (use "Re-run", because reports are cached for 12 hours). The render-blocking finding should list fewer files, or pass as "No render-blocking resources delay the first paint".
- FCP and LCP should drop by roughly the estimated savings. Compare two runs of the same device, not mobile against desktop.
- In the waterfall, the amber bars are gone from the start of the timeline.
- Re-run the unused code checker: the files you split should show a smaller unused share.
- Click through the pages that use the changed scripts: menus open, sliders slide, forms submit, checkout works. Open the browser console and look for red errors.
- Weeks later, check the field data. Lab numbers move the day you deploy; the Core Web Vitals that Google uses come from real Chrome visitors over 28 days.
Common mistakes
- Deferring the script that draws the first screen. If a slider or a JavaScript-rendered hero is above the fold, deferring its script leaves an empty box until it runs, and LCP gets worse. Keep that one early, or render the first slide in HTML.
asyncon scripts with dependencies. A jQuery plugin loaded withasyncmay run before jQuery and fail with "jQuery is not defined", but only sometimes, depending on which file arrives first. Usedeferfor both.- Inlining the whole stylesheet. Putting 150 KB of CSS in
<head>moves the problem into the HTML and stops the browser from caching it. Inline only the first screen's rules. - Removing jQuery because a plugin "does not need it". Many WordPress plugins and themes still call jQuery, sometimes only in an inline script. If something stops working after an optimisation, re-enable jQuery first.
- Stacking two optimisation plugins. WP Rocket plus Autoptimize plus the host's own optimiser each rewrite the same tags, and the result is unpredictable. Keep one.
- Chasing a zero. A page with one small, well-cached stylesheet will often still show it as render-blocking. If the estimated saving is under about 100 ms, move on to the server, the hero image or the JavaScript.
Questions people ask
What is render blocking?
Render blocking is when the browser has to wait for a file before it can draw anything on screen. Stylesheets in the page head and ordinary scripts without defer or async are the usual causes: the browser downloads and processes them first, and the visitor sees a blank page meanwhile. Each blocking file delays First Contentful Paint and, through it, Largest Contentful Paint.
Do render-blocking resources affect SEO?
Indirectly, yes. Google does not rank on the number of blocking files, but it does use Core Web Vitals, including Largest Contentful Paint, as a ranking signal, and a blocked first paint delays LCP. The bigger cost is visitors: a page that stays blank for two or three seconds on a phone loses people before they read anything. Fix them for speed, and the search benefit follows.
Is inline CSS render-blocking?
Inline CSS in a style tag does not cost an extra request, so it does not wait on the network the way a linked stylesheet does. The browser still has to parse it before painting, so a large inline block slows the first paint and bloats every HTML response. Inline only the few kilobytes the first screen needs and load the rest from a cached file.
Should every script get the defer attribute?
Almost every script can, but not quite all. Defer suits your own scripts and anything that depends on another file, because it keeps their order. Scripts that draw the first screen, such as a JavaScript-rendered hero, should stay early or be replaced with HTML. Independent third-party tags can use async instead. Inline scripts ignore both attributes, so check code that calls a deferred library.
Why does the speed test still show render-blocking files after I fixed them?
Usually because the page served to the test is not the one you changed. Reports are cached for 12 hours, so use Re-run; your page cache or CDN may also still hold the old HTML, so purge it. If the list is shorter but not empty, the remaining files may be ones you chose to keep, such as a small main stylesheet, which is fine when the saving is small.
Can I remove all render-blocking resources?
You can, but it rarely pays to. A page with no blocking CSS at all needs every rule for the first screen inlined, which makes the HTML bigger and harder to cache. The practical target is no blocking scripts, one small blocking stylesheet or an inlined critical block, and no unused plugin files. Past that point, the server response time and the hero image usually matter more.