"Eliminate render-blocking resources" is one of the most common warnings in PageSpeed Insights, and one of the most misunderstood. It does not mean you should defer everything; that is how sites end up with unstyled flashes and broken menus. It means the browser is waiting on files that are not needed for the first screen of content. This guide explains exactly what blocks rendering, which fixes are safe, and how to apply them with copy-paste code.
Key takeaways
- Render-blocking resources are stylesheets and head scripts without async or defer that make the browser wait before painting.
- Default to defer for your own scripts and use async only for independent scripts such as analytics.
- Inline a small amount of critical CSS for the first screen and load the rest of your stylesheet without blocking rendering.
- Self-host web fonts as WOFF2, preload the one or two used above the fold and set font-display to swap or optional.
What "render-blocking" means
When the browser parses your HTML, some resources force it to stop and wait before it can paint anything:
- Stylesheets linked with
<link rel="stylesheet">that apply to the current device. The browser will not paint until it has them, to avoid showing unstyled content. Amedia="all"stylesheet (or one with nomediaattribute) blocks rendering. - Scripts in the
<head>withoutasyncordefer. The parser stops, downloads the script, runs it, and only then continues.
Every millisecond spent waiting on these files delays First Contentful Paint and Largest Contentful Paint. In Lighthouse 13 the old audit has moved into the "Render-blocking requests" insight, which lists each blocking file and an estimate of the time it costs.
Step 1: List what is blocking
Open PageSpeed Insights or Lighthouse and expand the render-blocking requests insight. You will typically see:
- Your theme's main stylesheet
- Plugin or page builder stylesheets
- Web font CSS (Google Fonts or similar)
- jQuery and other scripts loaded in the
<head> - Third-party scripts: tag managers, A/B testing, cookie consent
For each one, ask: is this needed to render what the visitor sees first? Your main layout CSS, yes. A slider script for a carousel halfway down the page, no.
Step 2: Fix blocking JavaScript with defer and async
This is usually the easiest and safest win.
<!-- Blocks parsing: avoid in the head -->
<script src="/js/app.js"></script>
<!-- Downloads in parallel, runs after HTML is parsed, in order -->
<script src="/js/app.js" defer></script>
<!-- Downloads in parallel, runs as soon as it arrives, in any order -->
<script src="https://analytics.example.com/tag.js" async></script>
| Attribute | When it runs | Order kept | Use for |
|---|---|---|---|
| none | Immediately, blocks parsing | Yes | Almost never in the <head> |
defer |
After the HTML is parsed, before DOMContentLoaded |
Yes | Your own scripts, anything that touches the DOM or depends on another script |
async |
As soon as it downloads | No | Independent scripts like analytics |
type="module" |
Deferred by default | Yes | Modern bundles |
Practical rules:
- Default to
deferfor your own scripts. It keeps execution order, so jQuery still runs before the plugins that need it. - Use
asynconly for scripts that depend on nothing and nothing depends on them. - Inline scripts cannot be deferred.
deferandasynconly work on scripts with asrc. If an inline script callsjQuery(...)and jQuery is now deferred, it will error. Move such code into a deferred file or wrap it in aDOMContentLoadedlistener that runs after deferred scripts. - Remove what you do not need. The fastest script is one that does not load. Unused tags in a tag manager and plugin scripts on pages that do not use them are common.
On WordPress, most optimization plugins offer a "defer JavaScript" option, and since WordPress 6.3 developers can register scripts with a loading strategy:
wp_enqueue_script( 'my-slider', get_theme_file_uri( 'js/slider.js' ), array(), '1.0', array(
'strategy' => 'defer',
'in_footer' => true,
) );
Step 3: Inline critical CSS and load the rest later
CSS is harder because you cannot simply defer it: the page would render unstyled and then jump, which hurts both appearance and CLS. The technique is to split it:
- Critical CSS: the minimum rules needed to style the content visible on first load (header, navigation, hero, fonts). Inline it in the
<head>. - Everything else: load it without blocking rendering.
<head>
<style>
/* Critical CSS: header, nav, hero, base typography */
body { margin: 0; font-family: system-ui, sans-serif; }
.site-header { display: flex; align-items: center; height: 64px; }
.hero { min-height: 60vh; }
</style>
<link rel="preload" href="/css/main.css" as="style"
onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/css/main.css"></noscript>
</head>
The preload fetches the full stylesheet at high priority without blocking rendering. When it finishes, the onload handler switches it to a stylesheet. The <noscript> fallback covers visitors without JavaScript.
Caveats:
- Inline
onloadhandlers can be blocked by a strict Content Security Policy. See our CSP guide if you use one. - Extracting critical CSS by hand is error-prone. Tools like Critical (an npm package) generate it automatically, and many WordPress optimization plugins and some hosts offer it as a feature.
- Keep critical CSS small. Inlining your entire stylesheet just moves the problem into the HTML.
- web.dev itself describes this as an advanced technique and notes that many sites can meet their targets without it. Try the simpler fixes first.
Step 4: Use media attributes for conditional CSS
Stylesheets that only apply in certain conditions do not need to block rendering. Give them an accurate media attribute and the browser downloads them at low priority without waiting:
<link rel="stylesheet" href="/css/main.css">
<link rel="stylesheet" href="/css/print.css" media="print">
<link rel="stylesheet" href="/css/wide.css" media="(min-width: 1200px)">
Only stylesheets whose media query matches the current device block rendering.
Step 5: Avoid @import in CSS
@import inside a stylesheet creates a chain: the browser must download the first file before it even discovers the second. Replace imports with separate <link> tags in the HTML, or combine the files in your build step.
/* Avoid: discovered only after style.css downloads */
@import url("/css/typography.css");
Step 6: Load web fonts without blocking text
Font CSS from services like Google Fonts is a render-blocking stylesheet on a different domain, which also requires a new connection. Then the font files themselves can hide text while they load.
Options, from simplest to best:
Add a preconnect if you use a third-party font service:
<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=Inter:wght@400;700&display=swap">
Self-host the fonts instead. Serving WOFF2 files from your own domain removes the extra connections and the external CSS file:
@font-face {
font-family: "Inter";
src: url("/fonts/inter-400.woff2") format("woff2");
font-weight: 400;
font-display: swap;
}
Preload the one or two font files used above the fold (the crossorigin attribute is required for font preloads, even on the same domain):
<link rel="preload" href="/fonts/inter-400.woff2" as="font" type="font/woff2" crossorigin>
Set font-display. swap shows fallback text immediately and swaps when the font arrives. optional uses the web font only if it is available almost immediately, which avoids layout shifts. See fixing CLS for matching fallback font metrics.
Also limit weights and styles. Every weight is a separate file.
Step 7: Check you did not break anything
After deferring scripts and CSS, test:
- Menus, sliders, forms and checkout on mobile and desktop
- A hard refresh with cache disabled, watching for a flash of unstyled content
- The browser console for errors like "jQuery is not defined"
- Your analytics and conversion tracking still firing
Then re-run Lighthouse and compare FCP and LCP against your baseline.
Frequently asked questions
Should I defer all JavaScript?
Defer everything that is not needed for the first render, but test carefully. Scripts that inline code depends on, and anything required to display above-the-fold content, need special handling. Deferring a script that renders your hero, for example, can make LCP worse.
Is it safe to move scripts to the footer instead of using defer?
Placing scripts at the end of the <body> also avoids blocking the first render, but the browser discovers them later. defer in the <head> lets them download early while still running after parsing, which is usually better.
Why does PageSpeed still flag my CSS after I minified it?
Minification reduces file size but does not change whether a file blocks rendering. The file is still a full stylesheet the browser must download before painting. Inline critical CSS and load the rest asynchronously to remove the warning.
Do Google Fonts block rendering?
The Google Fonts stylesheet is render-blocking like any other stylesheet, and it lives on a separate domain. Preconnect helps, and self-hosting the font files with font-display: swap or optional usually removes the issue.
What to do next
Start with scripts: add defer to everything in the <head> that is not needed for the first paint, and remove tags you no longer use. Then tackle fonts, and only then consider critical CSS. Run a free audit before and after to check FCP and LCP moved. For the bigger picture, see improving Largest Contentful Paint and fixing slow INP.
Sources
Written by the MakeItFlow team
We build the website audit these guides are based on. Every fact is checked against the primary sources listed above . How we research and check
Check your own site in 30 seconds
Run a free audit covering speed, SEO, DNS, email authentication and security headers — and get a prioritised fix list.