
Render-Blocking Resources: What Actually Blocks, and Why async Is Not the Fix
- vuetelemetry
- Guides
- 8 min read
Lighthouse names the audit and lists the files. It does not explain that a script with neither attribute stops the parser dead, that async still blocks rendering when it executes, and that CSS blocks for a different reason entirely.
"Eliminate render-blocking resources" is the most common audit in a Lighthouse report, and the least explained. It names the files and stops there. Two different mechanisms are hiding behind that one line, and they need different fixes.
Scripts: what each attribute really does

**A classic script with no attribute stops the parser.** MDN states it plainly: scripts without `async`, `defer` or `type="module"` are *"fetched and executed immediately before the browser continues to parse the page"*. The browser is building your document, it meets the tag, and it stops - fetch, execute, then resume. Everything below that line waits.
**`defer` is the answer most of the time.** The attribute indicates the script *"is meant to be executed after the document has been parsed, but before firing DOMContentLoaded"*. Parsing continues uninterrupted, and deferred scripts *"execute in the order in which they appear in the document"* - so a script that depends on another still gets it.
**`async` is the one people misuse.** It is fetched *"in parallel to parsing and evaluated as soon as it is available"*, which sounds strictly better. The part that gets dropped is the sentence right after: *"once the download is complete, the script will execute, which blocks the page from rendering"*.
So async removes the blocking during the **download**, not during the **execution**. A large script fetched with async lands whenever it lands, in the middle of parsing, and blocks the render then. And because execution order follows arrival order, MDN warns there is *"no guarantee that scripts will run in any specific order"* - which breaks anything with dependencies.
The short rule: **defer for your own scripts, async only for genuinely independent third parties** like an analytics beacon that talks to nobody.
CSS blocks for a different reason
**CSS blocks for a different reason, and the fix is not an attribute.** A stylesheet does not stop the HTML parser, but the browser will not paint until it has the CSS it needs, because painting first and restyling after would show a flash of unstyled content. That is a rendering block, not a parsing one.
- No attribute: fetched and executed immediately, parsing stops
- defer: executes after parsing, in document order, before DOMContentLoaded
- async: no download block, but execution still blocks rendering, and order is not guaranteed
- CSS: does not block parsing, blocks painting - scope it with media instead
- Third-party tags are usually the largest share of the audit
The lever there is scope rather than timing. A stylesheet that only applies to one context can carry a `media` attribute, and the browser will fetch it without letting it block the first paint - `media="print"` being the well-known case. What remains is the CSS the first screen genuinely needs, which is the part worth keeping small.
Where the weight usually is, and how to confirm
**Third-party tags are usually the real weight.** Your own bundle is visible to you and gets optimised; the tag manager, the chat widget and the consent banner arrive as scripts you did not write, often loading further scripts of their own. Auditing what they actually cost - and whether each one earns it - moves the number more than shaving kilobytes off your own code.
**Then measure on the field, not on the report.** Lighthouse tells you a resource is render-blocking; it does not tell you whether removing it changed anything for your visitors. That difference between a lab audit and real users is [its own subject](/articles/field-data-vs-lab-data), and it is the one that decides whether the work was worth doing.
FAQ
What does 'eliminate render-blocking resources' mean?
It is a Lighthouse audit listing scripts and stylesheets that delay the first paint. Two different mechanisms sit behind it: a classic script with no attribute stops the HTML parser outright, while a stylesheet lets parsing continue but prevents painting until it has loaded. They need different fixes, which is why the audit alone is not enough to act on.
Should I use async or defer?
defer for your own scripts, async only for genuinely independent third parties. defer executes after parsing, in document order, so dependencies still work. async executes as soon as the download completes, which blocks rendering at that moment, and MDN warns there is no guarantee scripts will run in any specific order.
Does async make a script non-blocking?
Not entirely, and this is the common misreading. async removes the block during the download - the script is fetched in parallel with parsing. But MDN states that once the download is complete, the script executes and that execution blocks the page from rendering. A large async script simply blocks at an unpredictable moment instead of a predictable one.
Why does CSS block rendering?
Because painting before the stylesheet arrives would show unstyled content, then re-render it. So the browser waits. Unlike a script, CSS does not stop the HTML parser - it stops the paint. The fix is not an attribute but scope: a stylesheet limited by a media attribute is fetched without holding up the first paint.
How do I know if fixing it helped?
Not from the Lighthouse report, which only tells you a resource was render-blocking. Whether the change reached your visitors shows up in field data, at the 75th percentile over 28 days. A lab audit identifies the candidate; only the field confirms the result.



The lever there is scope rather than timing. A stylesheet that only applies to one context can carry a `media` attribute, and the browser will fetch it without letting it block the first paint - `media="print"` being the well-known case. What remains is the CSS the first screen genuinely needs, which is the part worth keeping small.