Performance 10 min read

Render-blocking resources: how CSS, JavaScript and fonts delay your pages

Why CSS, scripts and fonts hold up the first paint, and how to fix them without breaking things.

On this page 9 sections

PageSpeed Insights flags “render-blocking requests” (older reports say “Eliminate render-blocking resources”), lists a few CSS and JavaScript files and estimates how much time you’d save. It doesn’t say why those files hold the page up, which are safe to change, or why the obvious fix sometimes breaks the mobile menu.

Here’s how the browser builds a page, how to find the blocking files, and the standard fixes from web.dev and MDN, with their trade-offs.

The short answer

Render-blocking resources are files the browser must fetch, and sometimes run, before it paints the first pixels of a page. The usual suspects:

  • Stylesheets in the page head. The browser won’t paint until it has the CSS it needs.
  • Scripts without defer or async. They stop the browser reading the HTML until they have run.
  • Web fonts. They don’t block the page, but they can hide its text.

The fixes: defer scripts the first screen doesn’t need, inline a little critical CSS, stop loading CSS a page never uses, and load fonts deliberately. The goal is a small, fast set of blocking files, not zero.

How the browser builds a page, and where it waits

CSS blocks the first paint by design

The browser reads the HTML to build the Document Object Model (DOM), its map of the page, and turns the CSS into a matching model of styles (the CSSOM). Only with both can it lay out and paint the page.

Painting earlier would flash unstyled content, so the browser waits for every stylesheet in the head that applies to the current screen. One large or slow stylesheet delays the first paint for everyone.

Synchronous scripts stop the reading altogether

A classic <script src="…"> tag with no attributes is parser-blocking: the browser stops building the DOM, downloads the file and runs it before carrying on, because the script might change the page.

A subtler wait follows: a script may ask about styles, so the browser won’t run it until the stylesheets above it have loaded. A stylesheet followed by a synchronous script therefore holds up the HTML, not just the paint. Interleave several of each in the head and, on a slow mobile connection, the blank screen can last seconds.

Fonts delay text rather than the page

The browser only requests a font once it knows text on the page uses it. Until the file arrives, many browsers hide that text for up to about three seconds, so the headline, often the Largest Contentful Paint (LCP) element, stays invisible.

What the preload scanner can’t see

Browsers soften this with a preload scanner, which skims ahead through the HTML and starts downloads while the main parser is blocked. It can’t see files referenced inside CSS (an @import, a background image, a font) or added by JavaScript, so those load late, in chains.

How to find the render-blocking resources on your site

PageSpeed Insights and Lighthouse. Since Lighthouse 13, the files appear in the “Render-blocking requests” insight; older reports list them under “Eliminate render-blocking resources”. How to read a PageSpeed Insights report explains how far to trust the estimated savings, and why a large “element render delay” in the LCP breakdown often points here.

Chrome DevTools. The Performance panel records a load and flags render-blocking requests in its Network track. The Coverage panel (under More tools) shows how much of each CSS and JavaScript file the page used. In the Network panel, right-click a request, block it and reload to see what breaks without it.

A console check. In Chrome or Edge, performance.getEntriesByType('resource').filter(r => r.renderBlockingStatus === 'blocking') lists the files treated as blocking. At the time of writing, only Chromium-based browsers support it.

Test logged out: logged-in WordPress sessions usually skip the page cache and load admin toolbar files.

Fixing JavaScript: defer, async or neither

Most scripts on a business website don’t need to run before the first paint. Menus, sliders, form validation and analytics can all wait.

Loading option Blocks HTML parsing? When it runs Keeps order? Typical use
No attribute Yes Immediately, where it sits Yes The rare script that must run first
defer No After the HTML is parsed Yes Most of your own scripts
async Only while it runs As soon as it downloads No Independent scripts like analytics
type="module" No Like defer, unless async is added Yes Modern bundled code

Make defer in the head your default, so the file downloads early while the browser keeps parsing. Keep async for scripts that depend on nothing and that nothing depends on. Deferred code still runs on the main thread, where long tasks delay taps and hurt Interaction to Next Paint (INP), as Core Web Vitals explained covers.

When deferring a script breaks things

Some code depends on the old timing:

  • Inline scripts that call a library. An inline jQuery(function () { … }) runs immediately, so once jQuery is deferred the console reports “jQuery is not defined”. Adding defer to an inline script does nothing.
  • Order under async. A plugin script that runs before its library arrives fails intermittently, which is hard to reproduce.
  • DOMContentLoaded in async scripts. An async script can run after that event has fired, so code waiting for it never runs.
  • document.write. MDN notes that browsers ignore it in deferred and async scripts.
  • Scripts that block on purpose, such as consent managers, anti-flicker snippets for A/B tests and tiny scripts that set a colour theme before paint. Keep them small, first and few.

Since version 6.3, WordPress lets themes and plugins register scripts with a defer or async strategy, and falls back to normal loading where deferring would break the dependency order. That’s safer than forcing defer onto everything with a plugin.

Be careful with “delay until interaction”

Some optimisation plugins hold scripts back until the visitor first scrolls, taps or moves the mouse. Lab scores jump because the test never interacts, but visitors pay: the delayed code runs on their first tap, which can hurt INP; analytics can miss visitors who leave without interacting; and a JavaScript slider or mobile menu can look broken. Use it for chat widgets and embeds, not core features. Third-party scripts and site speed covers tags and widgets you don’t control.

Fixing CSS: critical CSS and unused CSS

Some CSS must block, so make that part small.

Inline the critical CSS

Critical CSS is the minimum needed to style the first screen: header, hero, typography and layout. Put it in a <style> block in the head so it arrives with the HTML. Guidance on web.dev suggests keeping above-the-fold content, inlined CSS included, under about 14 KB compressed: roughly what a new connection delivers in its first round trip.

The trade-offs: inlined CSS isn’t cached, so it travels with every page view. It’s per template, since a homepage’s critical CSS isn’t a service page’s. And it goes stale: generated from a snapshot at one or two screen sizes, it can miss styles after a template changes, causing unstyled flashes or layout shifts.

If a site’s total CSS is only a few kilobytes, inlining all of it is often simplest: nothing blocks and nothing needs generating.

Load the rest without blocking

The common patterns load the full stylesheet with rel="preload" or media="print", switch it on with an onload handler and add a <noscript> fallback. A strict Content Security Policy blocks inline handlers like these, so check your headers first.

Stylesheets whose media attribute doesn’t match the device still download, at lower priority, but don’t block rendering. Moving print styles, or styles only wide screens need, into their own files with a media attribute is a low-risk gain. And avoid @import inside stylesheets: the imported file can’t start downloading until the file that imports it has arrived.

Reduce unused CSS

Themes and plugins often load every stylesheet everywhere: slider styles with no slider, form styles on every page for one form. The Coverage panel shows the waste, but records one page in one state, so CSS that looks unused may style the open mobile menu, a form error or another template.

  1. Stop loading it where it isn’t needed. Load plugin and component styles only on templates that use them: the safest fix, and usually the biggest.
  2. Strip it with a tool. Automatic “remove unused CSS” features delete selectors they can’t find in the page, including classes JavaScript adds later. Keep a safelist and test every state.

Loading web fonts well

Web typography for business websites covers choosing and licensing fonts. For web font performance, the delivery decisions are:

  • Load fewer files. Each family, weight and style is usually a separate file; one variable font can replace several weights.
  • Self-host WOFF2 where you can. A hosted font service usually adds its own stylesheet: one more render-blocking request, to another server, before the font files themselves.
  • Subset by script. On multilingual sites, split fonts into subsets declared with unicode-range; the browser then downloads only the subsets for characters the page actually uses.
  • Preload only what the first screen needs, with <link rel="preload" href="/fonts/brand.woff2" as="font" type="font/woff2" crossorigin>. The crossorigin attribute is required even on your own domain, or the font downloads twice. Preload too much and you delay the hero image.
  • Choose a font-display value:
font-display value While the font loads If it arrives late Good for
block Text hidden, up to a few seconds Swaps in Icon fonts (SVG icons are better)
swap Fallback font at once Swaps in Most headings and body text
fallback Fallback after a brief wait Used only within a few seconds Balancing brand and stability
optional Fallback after a brief wait Not swapped in; often cached for the next page Stability above all

With font-display: swap, pick a fallback with similar proportions and tune it with the size-adjust descriptor, so text shifts less when the web font arrives.

Why page-builder sites need extra care

A typical page-builder page loads the builder’s framework CSS and JavaScript, styles for every widget in use, an icon font, spare font weights and scripts from several plugins. Widget snippets often assume jQuery is loaded.

So blanket settings are risky. “Defer all JavaScript”, “remove unused CSS” and “combine files” together can produce a great score and a contact form that fails on some templates or devices. Two optimisation plugins doing the same jobs make it worse.

If your builder can load assets only where they’re used, turn that on first. Then change one thing at a time on a staging copy, and after each change check:

How to speed up a WordPress website applies the same care to plugins, caching and hosting.

A safe order of work

  1. Measure LCP from field data if you have it, plus a lab baseline from three runs.
  2. List the blocking files and whether the theme, a plugin or an outside service loads each.
  3. Remove before you optimise: files from tools nobody uses are the cheapest fix.
  4. Defer your own scripts, fixing inline dependencies.
  5. Trim and split CSS, then inline critical CSS on key templates.
  6. Sort out fonts: fewer files, one or two preloads, a deliberate font-display.
  7. Re-test, then watch field data: it covers a rolling 28 days, so allow about four weeks.

What to do next

Render-blocking resources are rarely one bad file. They’re the sum of themes, plugins, fonts and scripts added over the years, and many are design decisions in disguise: four font families, a full-screen slider, an animation library used for one fade. The website speed optimisation guide ranks them alongside every other speed fix.

When the blocking files trace back to the theme or page builder itself, settings only go so far. When we design and build a WordPress website, speed is in the brief from the first design decision, alongside structure, content and conversion, rather than patched after launch. To see whether this is where your pages lose time, start with a free website audit.

Written by the PORVIX team

The people who design, build and maintain websites for growing businesses. We write about the questions that come up on real projects, in plain language, and update articles when the advice changes.

Published

How we work

Is your current site costing you enquiries?

A free, plain-language audit, by email within 2 business days.

Get a free audit
Get a free audit

Keep reading

More plain-language guides.

More on performance first, then other guides worth reading next.

All insights

Start here

Let’s build a website that brings in business.

Tell us about your project. You’ll hear back from a real person within one business day, with honest advice either way.

  • Free consultation
  • Fixed written quote
  • Your details stay private