How to Eliminate Render-Blocking Resources (CSS, JS and Fonts)

What makes CSS and JavaScript render-blocking, and how to fix it with critical CSS, defer and async, media attributes and smarter font loading.

By the MakeItFlow team 7 min read
Share LinkedIn X Facebook WhatsApp
How to Eliminate Render-Blocking Resources (CSS, JS and Fonts)

"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. A media="all" stylesheet (or one with no media attribute) blocks rendering.
  • Scripts in the <head> without async or defer. 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 defer for your own scripts. It keeps execution order, so jQuery still runs before the plugins that need it.
  • Use async only for scripts that depend on nothing and nothing depends on them.
  • Inline scripts cannot be deferred. defer and async only work on scripts with a src. If an inline script calls jQuery(...) and jQuery is now deferred, it will error. Move such code into a deferred file or wrap it in a DOMContentLoaded listener 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:

  1. Critical CSS: the minimum rules needed to style the content visible on first load (header, navigation, hero, fonts). Inline it in the <head>.
  2. 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 onload handlers 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

#render-blocking resources #critical css #defer #async #web fonts

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

Share LinkedIn X Facebook WhatsApp

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.

Weekly newsletter

One practical website fix, every week

New guides on speed, SEO, email deliverability and security — straight to your inbox. No spam, unsubscribe in one click.

Keep reading