Render blocking resources: find them, and stop waiting on the wrong ones

Render blocking resources are the stylesheets and scripts a browser must download before it can paint the first pixel. This chapter lists them, separates the ones that actually cost you from the ones that only look expensive, and gives the two fixes that remove the wait — plus a one-line command that produces the list for any URL.

Implementation8 min read2234 views
Render blocking resources: find them, and stop waiting on the wrong ones

Render blocking resources are the stylesheets and scripts a browser must download and process before it can paint the first pixel. The reason is not a bug: the browser is protecting you from showing a page without its styling. The mistake is treating all of them as equally worth fixing. This chapter lists them, ranks them by what they actually cost, and removes the wait from the two that matter.

Read this first

You need one live page and a terminal. Nothing to install beyond the command below. If you have not read it yet, largest contentful paint is the metric this work is aimed at; render blocking resources are one of its most common causes. For what a panel of real sites does with its stylesheets, render blocking css on 26 homepages is the measured companion, and the script half of the same problem sits in unused JavaScript across the same panel.

Why a resource blocks the first paint

A browser cannot paint a complete page until it has both the DOM and the CSS object model. The DOM it builds as it parses your HTML. The CSS object model it can only build after every stylesheet it considers render-blocking has arrived, because a rule can change how anything already parsed looks. So a stylesheet in the head stops the first paint until it downloads and parses. Nothing is drawn early and corrected later; the browser simply waits.

A plain <script> blocks differently. It stops the HTML parser itself, because the script might write into the document. That delays the DOM, and it delays the paint behind it. A script with defer or async, or a module, downloads without holding the parser. Two mechanisms, one visible symptom: a blank screen while something on the network is still talking.

A render blocking resource is not slow. It is early. You fix it by changing when it is needed, not by making it smaller.

That distinction is what makes this topic worth a chapter. Compressing a blocking stylesheet is almost never the fix, because the wait is about ordering, not size. The fix is to decide which resources genuinely have to arrive before the first paint and to move the rest out of the way.

Do it in this order

Five steps, and each one ends in something you can check. Do not skip step two: it is what stops you optimising a file that was never the problem.

  1. List the blocking requests. Run the command below against the live URL. Done when you have a list of stylesheets and head scripts with a byte size next to each.
  2. Separate what must block from what must not. Mark each resource as needed for the first screen or not. Done when the "not" column is non-empty; on most sites it is the majority.
  3. Fix the stylesheet side. Inline the rules the first screen needs, and load the rest without holding rendering. Done when the head no longer contains a render-blocking link to the deferred sheet.
  4. Fix the script side. Move blocking head scripts to the end of the body, or add defer. Done when no plain script sits between the opening <head> and the first paint.
  5. Re-run and compare. The same command, the same page. Done when the request list is shorter and the largest contentful paint has moved earlier.

Step three is the one people get wrong most often, because the obvious move is to inline everything. That trade is real and it has a cost, which the failure section below spells out.

How to find your render blocking resources

The quickest list comes from Lighthouse's render blocking requests audit, which prints each blocking URL, its transfer size and the time it held up the render. It is one command, and it reads the same JSON the browser tooling prints.

npx lighthouse https://example.com/ \
  --only-audits=render-blocking-insight \
  --output=json --output-path=blocking.json --quiet

Run against github.com on 2 October 2026, the audit returns 20 render blocking requests. Every one of them is a stylesheet and not one is a script, which is the first useful thing the list tells you: the JavaScript on that page is already out of the way. The 20 sheets carry 279 KB between them. The largest is a 72 KB theme stylesheet, and the audit attributes 313 ms of blocking to it.

The one that cost the most was not the largest. A 7.5 KB sheet, roughly a tenth the size, is credited with 1,765 ms — more than five times the 72 KB file.

ResourceSizeBlocking
primer-react-brand-css.module.css72,143 B313 ms
primer-4136ede8.css43,599 B313 ms
global-54ba76e9.css40,846 B157 ms
primer-react-css.module.css35,230 B157 ms
light-99f877e9.css7,574 B1,765 ms

Read one page, fetched once, as a demonstration of the command, not as a score for the whole site. The size column is the part you can act on, because it is the same every time. The duration column moves with the network, and on this run it says a small file can hold the render longer than a large one when it is discovered late or served slowly — a single reading is not a measurement of your site.

The deliverable: a table for deciding, not a number

The number of blocking resources is not itself useful. What you act on is the pairing of "does this have to be here before the first paint" with "how expensive is it to leave it here". Every blocking resource you meet falls into one of four rows.

ResourceWhy it blocksWhat to do
Stylesheet for the first screenCSSOM is needed before paintKeep it, but keep it small; this is the one that earns its place
Stylesheet for below the foldSame rule, but the rules are not needed yetDefer it, or split the critical rules out and inline those
Plain <script> in the headStops the parser, so the DOM stops tooAdd defer; if it truly must run first, keep it tiny
Third-party tag loaded earlySounds small, often is not, and you rarely control itLoad it after first paint, or not at all

The single most useful habit is to sort the list by transfer size and then read the top of it out loud. A blocking resource that is both large and not needed for the first screen is the only kind worth a morning. The rest can wait for a slower day.

What goes wrong

Three failures show up again and again. All three look like progress in a browser on a fast machine.

  1. Inlining the entire stylesheet. Inlining removes the blocking request, so the first paint can be immediate. It also puts every byte of CSS into the HTML on every single navigation, where it cannot be cached on its own. On a site with a large sheet, you have traded a cacheable request for uncacheable weight on every page.
  2. Adding defer to a script that had to run first. A deferred script runs after parsing, so anything that depended on it running early now runs late, or races the rest of the page. Defer is the right move only for scripts the first paint does not depend on.
  3. Trusting the lab list as the site's verdict. A blocking resource on a fast CDN with a warm cache costs almost nothing, and the audit cannot tell that case apart from the expensive one. Use the list to decide what to move, then measure the field number to see whether moving it changed anything.

Common questions

How do I eliminate render blocking resources?

You do not eliminate all of them. You reduce the list to the resources the first screen genuinely needs, inline or preload those, and move the rest out of the critical path with media switching, defer, or by loading them after first paint. A page with zero blocking resources usually means everything was inlined, which has its own cost.

Does preload fix a render blocking resource?

No. Preload changes how early the browser starts fetching a file; it does not change whether the resource blocks rendering. A preloaded render-blocking stylesheet still blocks. Preload helps most when the resource is discovered late, such as a font named deep inside a stylesheet.

Is render blocking resources a ranking factor?

Not on its own. It matters because it delays the largest contentful paint, and Core Web Vitals feed the page experience signals. Google has never published a conversion from milliseconds of blocking to positions, so treat this as a user problem that is measured, not as a lever with a known price.

Does async remove render blocking?

For scripts, yes: async and defer both let the parser continue, so the script no longer blocks rendering. Neither attribute exists for stylesheets, which is why CSS needs a different fix. A stylesheet loaded with media="print" plus an onload that restores all is the pattern for that, and in our own survey of 26 homepages, none of them used it.

How many render blocking resources should a page have?

There is no target number, and anyone who gives you one is guessing. The useful question is whether each resource on the list is needed before the first paint. A short list of needed resources beats a long list of cheap ones, because the cost is the wait, not the count.

The boundary worth saying plainly: this chapter is about the browser's first paint. A crawler that fetches your HTML without rendering never waits for any of it, so a stylesheet that blocks a browser costs that client nothing. Whether a resource blocks is also a property of where it is discovered, so a sheet inserted by script needs blocking="render" to block at all — we have not tested that path, and we are not going to state a result we did not see. Keeping the blocking list short, and then watching the field number across the pages people land on, is part of what QueryWin works on.

Part of the QueryWin handbook · Level 2

Render blocking resources: find them, and stop waiting on the wrong ones