How to check render blocking resources

Scan the served <head> for stylesheet links and for scripts that carry no defer, async or type="module", then record a load in the Chrome DevTools Performance panel and read the render blocking list it produces. The markup scan over-counts, and the trace names what actually delayed the first paint.

Why check this

A render blocking request holds the first paint, so the reader sees nothing while it travels. Testers meet it as a white screen that lasts longer on a cold cache than anyone on the team ever sees. Run the check after a change to the head of the template, after a new analytics or font tag lands, and before sign-off on any page whose LCP is dominated by render delay.

Prerequisites

Steps

  1. Step 1.

    List the candidates in the head from the served markup, excluding print stylesheets and deferred scripts.

    node -e "const html = await (await fetch('https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS')).text(); const head = html.slice(0, html.indexOf('</head>')); const css = (head.match(/<link[^>]+rel=\"stylesheet\"[^>]*>/g) || []).filter(t => !/media=\"print\"/.test(t)); const all = head.match(/<script[^>]*\ssrc=[^>]*>/g) || []; const js = all.filter(t => !/\s(defer|async)\b/.test(t) && !/type=\"module\"/.test(t)); for (const t of css) console.log('css   ', (t.match(/href=\"[^\"]*\/([^\"\/]+)\"/) || [])[1]); for (const t of js) console.log('script', (t.match(/src=\"[^\"]*\/([^\"\/]+)\"/) || [])[1]); console.log('blocking candidates:', css.length + js.length); console.log('scripts skipped as non-blocking:', all.length - js.length);"
    
    css    styles-global.13616ebb1fc8ee2f.css
    css    270.66bae6059a1c23f9.css
    css    styles-reference-layout.632d2f3884f10ca3.css
    css    9228.c46ef79d5582674d.css
    css    styles-content-section.1eb240d1720d8ac3.css
    css    styles-heading-anchor.0eec5185ab9425b7.css
    …
    blocking candidates: 18
    scripts skipped as non-blocking: 4

    This is the pre-filter, and it runs anywhere, including in CI. It lists what could block. It cannot say what did.

  2. Step 2.

    Open the URL in Chrome, press F12, go to the Performance panel and click the reload and record button. Open the insight for render blocking requests and read both the list and the estimated savings.

    Render-blocking resources in <head>: 6
    styles-global.13616ebb1fc8ee2f.css
    270.66bae6059a1c23f9.css
    styles-reference-layout.632d2f3884f10ca3.css
    9228.c46ef79d5582674d.css
    styles-content-section.1eb240d1720d8ac3.css
    styles-heading-anchor.0eec5185ab9425b7.css
    
    Estimated savings: FCP 0 ms, LCP 0 ms
  3. Step 3.

    Compare the two lists before you write anything down. The scan named 18 files, the trace named 6, and the 6 are the first 6 of the scan in document order. Take the trace list, and read the estimated savings beside it.

    Savings of FCP 0 ms, LCP 0 ms mean the browser had already finished those requests by the time it wanted to paint. Six blocking requests existed and removing them would have gained nothing measurable on that load.

How to read the result

| What you see | What it means | What to do | | --- | --- | --- | | 18 in the scan, 6 in the trace | The scan lists candidates, the trace lists the requests the paint waited on | File against the trace list. Attach the scan as context. | | Estimated savings FCP 0 ms, LCP 0 ms | Removing them would not have moved this load | Do not file it. Record the measurement and close the question. | | Estimated savings of several hundred ms | The paint really waited | Inline the critical rules, or split the sheet the page needs first. | | A media="print" stylesheet in a report | Print styles do not block rendering | Exclude it, as the command does. | | A type="module" script flagged as blocking | Module scripts defer by default | Exclude it, as the command does. |

Common mistakes

Sign: A count above zero is filed as a performance defect.Cause: The captured page had 6 render-blocking requests and the trace put the estimated saving at 0 ms for both FCP and LCP, because those requests finished inside a 374 ms render delay. The count is not the defect. The delay is.
Sign: A markup scan reports three times as many blockers as the trace.Cause: 18 against 6 on the captured page. The scan counts every stylesheet link in the head, while the trace counts the requests still outstanding when the browser wanted to paint. Only a real load can separate the two sets.
Sign: A script tag with no defer and no async is reported as blocking, and it is not.Cause: A script with type=module is deferred by default. A scan that keys on defer and async alone flagged all four module scripts on the captured page, taking the candidate count from 18 to 22 for nothing.
Sign: The page loads fast for the team and slowly for a new visitor.Cause: A warm cache serves the blocking stylesheets from disk, so the paint waits for almost nothing. Test with Disable cache ticked in the Network panel, and read the field data, before accepting the result.

What to check next

FAQ

How to check render blocking resources without Lighthouse?

Step 1 needs nothing but Node and a network connection, and it runs in a pipeline. It gives a candidate list. For the list that matters you still need a real load, because blocking is a property of the load, not of the markup.

Are module scripts render blocking?

No. A script with type="module" is deferred by default, so it does not hold the first paint. The scan in step 1 excludes them, which is why its count is 18 rather than 22 on the captured page.

Can a stylesheet be made non-blocking?

Yes, by loading it with a media value that does not match, or by loading it after the first paint and applying it then. A sheet the first viewport needs should stay blocking, because the alternative is a flash of unstyled content.

How many render blocking requests are acceptable?

The count has no threshold. The captured page had six and lost nothing measurable. Judge by the estimated savings in the trace and by the render delay subpart of LCP, never by the number of files.

Verified

The browser figures in step 2 come from one Chrome DevTools capture of https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS on 2026-09-11, on one machine, with no CPU and no network throttling. The command in step 1 was run against the same URL on the same day. Read your own Chrome build from chrome://version and record it with your own numbers.

Verified by Maks Vernynode 22.23.2

Each output block is what the command above it printed on that date, on the host named in the step. Figures read from a live site move between runs. Compare the shape of the answer rather than the digits, and see the methodology for how a page is re-verified.

intermediate8 minpublished updated Maks Verny