SEO check

Render Blocking Resources: How to Find and Fix Them

Scripts and stylesheets that delay first paint. See how our free report detects them and how to defer, inline or trim each one.

Free to use 15 min read Updated Oct 2026

Render blocking resources: the quick answer

Quick answer: Render blocking resources are stylesheets and classic scripts that the browser has to fetch and handle before anything can appear on screen, so every one of them pushes first paint later. Our report opens your page in headless Chromium and raises a warning when it finds at least one.

  • Pass: the report finds zero blocking scripts or stylesheets when the page loads.
  • Warning: one or more blocking files are found. The row shows as a warning, never as a hard fail, and it still lowers your score.
  • Fix: add defer or async to scripts, inline only the small first screen CSS, and load the rest of your CSS without blocking.

Check my site free

You'll find this check in the Speed optimizations section of the report, next to the other loading tests.

What are render blocking resources?

A browser needs both the DOM and CSSOM before it can show a page. The DOM comes from your HTML and the CSSOM from your CSS, so blocking CSS pauses painting until the file is fetched and parsed. A classic script in the head does more, because it also stops the HTML parser until the script has downloaded and run. These files sit on the critical rendering path.

Picture a head holding one stylesheet followed by one classic script. The parser reaches the script tag and stops. The script downloads, and the browser holds it back until the stylesheet above it has loaded, since a script may ask for computed styles. Painting waits for that stylesheet, and the rest of the HTML waits for the script.

In practice two kinds of file count. The first is a stylesheet link tag whose media value matches the visitor's screen, for example no media attribute, media="all", media="screen" or a query the window satisfies. The second is a script tag in the head with no async attribute, no defer attribute and no script type module. A page can also opt in on purpose with the blocking="render" attribute, documented on MDN, which marks a file as render blocking.

Older tutorials call this the Eliminate render-blocking resources audit, which newer Lighthouse versions fold into Render-blocking requests. The fixes are identical.

Render blocking versus parser blocking

The two terms overlap but they are not the same. Parser blocking means the HTML parser cannot read any further down the document. Render blocking means the browser will not paint until the file has been processed. A classic head script is both. A stylesheet is render blocking, and it can also hold back any script after it, because that script may ask for computed styles.

What does not block rendering

We tested a probe page in the same Chromium build our report uses, and these cases were all marked as non blocking:

  • Scripts with defer. They download in parallel and run after parsing finishes, so rendering is not held up. A heavy deferred script can still tie up the main thread, but that shows in Total Blocking Time, not here.
  • Scripts with async. They download in parallel and run whenever they arrive, though one that lands early can interrupt parsing while it executes.
  • Module scripts. A script with type="module" behaves like a deferred one by default.
  • A print only stylesheet. With media="print" the file is irrelevant to the screen, so painting does not wait for it.
  • A stylesheet whose media query does not match. The browser fetches it at low priority and moves on.
  • A preload hint. A link with rel="preload" only fetches the file early, so it does not hold back painting.
  • Files at the end of the body. They cannot hold back content above them.

Why render blocking resources slow down your page

Until the blocking files arrive, your visitor sees a blank or half drawn screen. The browser waits for the slowest of them, and on a slow mobile connection that wait stretches.

The direct damage is a later First Contentful Paint and, often, a later Largest Contentful Paint. LCP is one of the Core Web Vitals. The hero image or headline that defines your LCP cannot paint before its CSS is ready. Our Largest Contentful Paint test measures that metric, and the Core Web Vitals guide covers all three.

SEO angle: Render blocking files are not a direct ranking penalty. They hurt through page experience signals and through people who leave before the page ever appears.

One small blocking stylesheet costs very little, so don't chase zero at any price. Our row warns at a single file, which means a site with one tiny critical sheet will keep a warning, and that is acceptable. The real target is fewer and smaller blockers, and the usual surprise is a third party tag such as a font stylesheet or a chat widget.

How SEO Site Checker tests render blocking resources

The server opens your URL in a fresh headless Chromium window sized 1280 by 720, with an empty cache and no throttling. It waits for the load event, for up to 20 seconds, and then reads the browser's own Resource Timing entries.

The exact rule: an entry counts when its initiator is a script or a link and Chromium marks its renderBlockingStatus as blocking. For every match the report keeps the URL, the type and the transfer size.

That property of the Resource Timing API is documented in the renderBlockingStatus reference, which lists its two values, blocking and non-blocking. We don't invent our own opinion about what blocks. We ask the browser, and the table shows common cases.

ResourceCounted by our check?Reason
Stylesheet link in the head with matching mediaCountedChromium marks it blocking
Classic script in the head without async, defer or moduleCountedChromium marks it blocking
Script with defer, async or type moduleNot countedChromium marks it non blocking
Stylesheet pulled in with an @import ruleNot countedIts initiator is css, and we only keep script and link
Fonts and imagesNot countedChromium marks them non blocking, and their initiator is css or img, not a script or link tag
The HTML document requestNot countedThe page itself is not a script or a stylesheet
Anything past the first 250 resource entriesNot countedThe browser's default timing buffer is full

What a pass means

A pass means the list came back empty, with no blocking stylesheet or classic script at load time. The row shows a green check. It does not prove the page is fast, because images, fonts and server response time sit outside this test. A surprising pass is worth confirming with the console snippet, since a failed timing read can also show as a pass.

What a warning means

A warning means at least one blocker was found. There is no size or time threshold, so a single 1 KB stylesheet is enough and many real sites will see a warning. The row shows a warning icon, the count, an expandable URL list (signed in users see the list) and a red How to Fix button.

Read the list before the count, because three small header stylesheets are a different problem from one huge slider script. A warning does not count as passed, so it lowers your score.

What the check does not see

CSS loaded through @import is reported by Chromium as blocking, yet its initiator is css, so our filter drops it. The list also stops at 250 resource entries: a test page with 300 blocking stylesheets returned exactly 250.

The environment differs too. PageSpeed Insights by default runs a throttled mobile emulation and shows estimated savings in milliseconds, while we give a plain list from a fast server connection, so the two can disagree. Use our report to find blockers and a throttled run to size the gain.

Run the free SEO score report

How to find render blocking resources on your site

Use at least two of these ways, because each measures slightly different things.

  1. Run the report. Open the Render Blocking Resources row and note the count and the file URLs. The URL list is visible to signed in users, and an account is free.
  2. Cross-check in PageSpeed Insights. Most of the same files should appear under Render blocking requests in the performance results, formerly titled Eliminate render-blocking resources. The Lighthouse render blocking audit documentation explains when a file is flagged.
  3. Ask the browser directly. Open Chrome DevTools, switch to the Console, wait for the page to load and paste the snippet below, which applies the same filter as our report. It works in Chromium based browsers only.
  4. Check Coverage and the Performance panel. In DevTools press Ctrl+Shift+P (Cmd+Shift+P on a Mac), type Show Coverage and reload. A blocker with mostly unused bytes is a candidate for trimming. The Performance panel marks First Contentful Paint, so you can see which blockers finish before it.
  5. Read the source. View the page source and look for head scripts without defer or async and for stylesheet links. Plugins and tag managers inject tags your template never held.
JavaScript
performance.getEntriesByType('resource')
  .filter(entry => entry.renderBlockingStatus === 'blocking'
    && ['script', 'link'].includes(entry.initiatorType))
  .map(entry => entry.name);

That snippet matches our report. To also see CSS pulled in through @import, which our report leaves out, run this variant, which prints each initiator before the URL. Entries marked css are the ones we skip.

JavaScript
performance.getEntriesByType('resource')
  .filter(entry => entry.renderBlockingStatus === 'blocking')
  .map(entry => entry.initiatorType + ' ' + entry.name);

Extensions, a warm cache and a different window size can change the rows, so treat the output as a cross-check.

How to eliminate render blocking resources, step by step

Work through the list in this order, because skipping the triage is how menus get broken.

  1. Triage the list. Label every blocker as critical or non critical. Critical files supply header, navigation and hero styles. Sliders, chat, analytics and below the fold widgets are not.
  2. Fix the scripts. Add defer to scripts that need the DOM or a fixed run order, and async to independent ones such as analytics.
  3. Fix the stylesheets. Put the small set of first screen rules in an inline style block in the head, then load CSS asynchronously for the rest. Samples follow below.
  4. Deal with fonts and third parties. Font stylesheets and chat widgets are the usual suspects.
  5. Shrink what is left. Remove unused CSS and JavaScript, then minify. Our free CSS minifier and JavaScript minifier do the compressing, the CSS minification guide covers the detail, and minify HTML, CSS and JavaScript shows the workflow.
  6. Re-test. Run the report again, then check a phone and a desktop for flashes of unstyled content or layout jumps.

Scripts: defer, async or inline

The decision rule is short. If the script needs the finished DOM, or depends on another script such as a library and its plugins, use defer, because deferred scripts keep their document order. If the script stands alone and nothing waits on it, such as an analytics snippet, use async. If the code is a few lines that must run before first paint, keep it inline, and accept that it blocks briefly.

HTML
<head>
  <script src="/js/jquery.min.js" defer></script>
  <script src="/js/jquery-plugin.js" defer></script>
  <script src="https://example.com/analytics.js" async></script>
</head>

The jQuery file and its plugin keep their order under defer, while analytics gets async because nothing depends on it.

Deferring a library has a side effect. An inline snippet that calls it, such as $(document).ready(...) after jQuery, now runs before the library exists and fails with a "$ is not defined" error. You have two good options and one fallback. Move the snippet into a deferred file listed after the library, or keep it inline and wrap it in document.addEventListener('DOMContentLoaded', ...), which works because deferred scripts finish running before that event fires. If neither is practical, leave that one library blocking and defer the rest.

Stylesheets: inline critical CSS, load the rest later

Critical CSS means only the rules the first screen needs, the above the fold content. Keep it to a few kilobytes, because inlined rules are downloaded again with every HTML response. The defer non-critical CSS article walks through it and recommends a tested helper such as loadCSS for production. The article itself labels this an advanced technique that can introduce bugs if done carelessly, and the swap below relies on an inline onload handler, which a strict Content Security Policy can block, so try it on staging first.

HTML
<head>
  <style>
    body { margin: 0; font-family: system-ui, sans-serif; }
    .site-header { background: #0b3f44; color: #fff; padding: 1rem; }
  </style>
  <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>
</head>

The noscript fallback keeps the page styled without JavaScript. A second option is splitting by media: a link with media="print" never blocks, and a query such as media="(min-width: 1400px)" does not block on a narrower viewport. Our test window is 1280 pixels wide, so a min-width: 1400px sheet would not be listed even though larger screens still wait for it. Use media splits for real device differences, never to hide your main stylesheet.

Fonts and third party tags

A preconnect hint and font-display: swap shorten the wait and avoid invisible text, but neither takes the Google Fonts stylesheet off the list. Only self hosting the font files or loading that stylesheet without blocking does. Start chat widgets after the first interaction or a short idle delay.

Fixing render blocking resources in WordPress, Shopify and other platforms

WordPress

From WordPress 6.3 you can pass a loading strategy in the arguments array of wp_enqueue_script. The separate in_footer option moves the tag to the end of the body instead.

PHP
add_action('wp_enqueue_scripts', function () {
    wp_enqueue_script(
        'theme-app',
        get_theme_file_uri('/js/app.js'),
        array(),
        '1.0',
        array('strategy' => 'defer')
    );
});

Stylesheets are the harder half. The style_loader_tag filter receives the finished link tag and its handle, so you can rewrite one theme stylesheet into the preload and swap form and keep a noscript copy. WordPress prints its link tags with single quotes, which is what the replacement below looks for.

PHP
add_filter('style_loader_tag', function ($html, $handle) {
    if ($handle !== 'theme-style') {
        return $html;
    }
    $async = str_replace(
        "rel='stylesheet'",
        "rel='preload' as='style' onload=\"this.onload=null;this.rel='stylesheet'\"",
        $html
    );
    return $async . '<noscript>' . $html . '</noscript>';
}, 10, 2);

The handle must match the one your theme passed to wp_enqueue_style, which you can read from the id attribute of the link tag in your page source, minus the -css suffix. Pair it with a small inline block of first screen rules through wp_head, or the unstyled flash shows.

WordPress quietly keeps a script blocking when another script that depends on it is blocking, so defer the whole chain, not just one handle. An inline snippet attached with wp_add_inline_script() has the same effect, because it defaults to the after position and keeps its parent script blocking. Pass before as the position, or move that code into a file of its own, and the defer should hold. That is the behavior documented for 6.3 and later releases may change it, so check the rendered script tag for the defer attribute on your version. On older versions, use the script_loader_tag filter to add the attribute yourself. If you prefer a plugin, LiteSpeed Cache offers deferred JavaScript, asynchronous CSS, a critical CSS generator and unused CSS removal, while Autoptimize can defer JavaScript and inline critical CSS. Menu labels shift between versions, so look for those settings by name. Switch on one option at a time, and exclude jQuery and slider scripts if something breaks.

Shopify

Open theme.liquid and add defer to the theme scripts you control. Remove code left by uninstalled apps, and use an installed app's own setting for scripts it injects.

HTML
<script src="{{ 'theme.js' | asset_url }}" defer></script>

Other builders and custom code

Wix, Squarespace and Webflow give you limited control, so remove unused apps and embeds and put custom code at the end of the body. On custom builds, split CSS per page. The Next.js script component has a strategy prop, such as lazyOnload for a chat widget.

JavaScript
import Script from 'next/script'

export default function Chat() {
  return <Script src="https://example.com/chat.js" strategy="lazyOnload" />;
}

Common mistakes when fixing render blocking resources

Before and after checklist: Check the menu, any slider, the checkout and the browser console on a phone and a desktop, before and after your change. A new "$ is not defined" error or a menu that stopped opening usually means a script lost the library it depends on.

  • Defer on the wrong code. Code that calls document.write can break the page outright once it runs late.
  • Inlining everything. A 300 KB stylesheet pasted into the head grows the HTML on every view and cancels the gain. Our inline CSS guide covers the trade offs.
  • Hiding stylesheets at the bottom. Links moved to the end of the body dropped off our probe list, but visitors then get a flash of unstyled content.
  • Missing the hidden ones. CSS pulled in through @import and the Google Fonts stylesheet are easy to overlook, and the @import case will not appear in our list at all, so run the second console variant above to see it.
  • Async on dependent scripts. Async scripts run in whatever order they arrive, so a plugin marked async can execute before the library it extends and throw an undefined error.
  • Trusting a plugin blindly. Optimization plugins change many files at once, so switch on one option at a time and run the checklist after each.

How to Fix

The short version, the same steps you see when you click How to Fix in your report.

Why this fails: Chromium found at least one stylesheet or classic head script that must finish before the page can paint. The report lists those files, and any single one is enough for a warning.

How to fix it:

  1. Find each file in the report list and decide whether the first screen really needs it.
  2. Add defer to scripts that need the DOM or a fixed order, and async to independent ones such as analytics. Move inline code that calls a deferred library into a deferred file after it, or you get "$ is not defined". Inline scripts ignore both attributes.
  3. Inline only the small first screen CSS in a style block in the head. A critical CSS generator or DevTools Coverage shows which rules those are.
  4. Load the full stylesheet with the preload and onload swap, and keep the noscript fallback.
  5. Test by loading the page and confirming the full styles still apply, because a strict Content Security Policy can block the inline onload handler. Then check menus and sliders.
HTML
<script src="/js/app.js" defer></script>
<script src="https://example.com/analytics.js" async></script>

<style>body { margin: 0; } .site-header { padding: 1rem; }</style>
<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>

On WordPress 6.3 or later, pass a strategy of defer in the args array of wp_enqueue_script:

PHP
wp_enqueue_script('theme-app', get_theme_file_uri('/js/app.js'), array(), '1.0', array('strategy' => 'defer'));
  • WordPress: use the code above. If the script stays blocking, defer the dependent scripts too and attach inline code with the before position. Or switch on one cache plugin option at a time.
  • Shopify: add defer to theme scripts in theme.liquid and remove code left by uninstalled apps.
  • Wix, Squarespace and Webflow: remove unused embeds and move custom code to the end of the body.
  • Custom build or framework: split CSS per page and use the framework's script loading strategy.

For the full technique, read defer non-critical CSS.

Re-test: clear site and CDN caches, then run our free report again. The check passes when its list shows zero files.

Re-test my website

Frequently Asked Questions

What are render blocking resources?
They are CSS files and classic scripts that the browser must fetch and process before it can paint anything, so they hold back first paint even when your server is fast. Scripts that use defer, async or type module, and print only stylesheets, do not block rendering.
How do I find render blocking resources on my website?
Run the free report and open the Render Blocking Resources row, where signed in users see the full URL list. Then cross-check in PageSpeed Insights under Render blocking requests, or filter the browser's own timing data in the DevTools console. Plugins and tag managers inject files that appear in all of them.
How do I eliminate render blocking resources in WordPress without a plugin?
From WordPress 6.3, pass a strategy of defer in the args array of wp_enqueue_script, and defer any scripts that depend on it. For CSS, print a small block of critical rules through the wp_head action, then use the style_loader_tag filter to turn the theme stylesheet link into a preload with an onload swap and a noscript copy. Test the menu and any slider afterwards, because a deferred script can lose a library it depended on.
Should I use async or defer for render blocking JavaScript?
Use defer for scripts that need the DOM or must run in order, such as a library and its plugins, because deferred scripts keep their document order. Use async for independent scripts like analytics, which run whenever they arrive. Module scripts already behave like defer, and neither attribute does anything on an inline script.
Are Google Fonts and other web fonts render blocking?
Yes, the Google Fonts stylesheet is render blocking, although the font files themselves are not. Its link is a normal stylesheet, so it shows in the report list. The display=swap parameter and preconnect help with invisible text and wait time, yet neither removes it from the list. To clear it, host the font files yourself or load that stylesheet without blocking.
Why does the report still show render blocking resources after I added defer?
The usual causes are stylesheets that still block, a plugin or tag manager injecting another script into the head, or an old cached copy of the HTML. In WordPress, a script also stays blocking when a dependent script is blocking, or when an inline snippet added with wp_add_inline_script sits after it. Purge every cache, then run the report again and read the exact URLs.
What is the difference between render blocking and parser blocking?
A script in the head stops the parser and delays the first paint, while a stylesheet stops painting and also holds back any script after it. Adding defer or async lifts a script out of both problems, but a stylesheet needs inlining or a non blocking load to get the same relief.
Do render blocking resources hurt SEO and Core Web Vitals?
They are not a direct ranking penalty, but they delay First Contentful Paint and often Largest Contentful Paint, which is a Core Web Vital. Visitors who give up before the page appears are lost whatever happens to rankings. Fewer and smaller blockers is the realistic aim, and third party tags are the first place to look when the count will not drop.

See how your site scores right now

Paste your address into the free report, then use the How to Fix button on anything that needs work.

Free to use. Results in under a minute for most sites.