Render-Blocking Resources
Date: 2026-08-16
Files the browser insists on having before it will paint anything. CSS blocks rendering, scripts block parsing, and both delay first paint — but the fixes are different because the reasons are different.
What it is
A render-blocking resource is one the browser must fetch and process before it can display content. Until it resolves, the user sees nothing.
Two distinct blockers, routinely conflated:
| Blocks | Why | |
|---|---|---|
| Stylesheets | Rendering | Painting before styles are known would show unstyled content, then rearrange it |
| Synchronous scripts | Parsing | The script could call document.write() and change the markup that follows |
A blocking script is worse, because the browser hasn’t even discovered the rest of the document yet — see Document Parsing.
Finding them
Everything in <head> that isn’t defer, async, or inline:
<head>
<link rel="stylesheet" href="/main.css"> <!-- blocks render -->
<link rel="stylesheet" href="/vendor.css"> <!-- blocks render -->
<script src="/analytics.js"></script> <!-- blocks parse -->
<script src="/tag-manager.js"></script> <!-- blocks parse -->
</head>Each is a round trip before first paint. On a mobile connection at 150ms latency, four sequential blockers is most of a second before anything can be drawn — and that’s before any of them have executed.
Lighthouse’s “Eliminate render-blocking resources” audit lists them and estimates the saving, which is a reasonable starting point even if the estimate is optimistic.
Fixing CSS
Inline what’s needed for the first screen, load the rest asynchronously.
<style>/* critical: layout, header, hero, above-the-fold typography */</style>
<link rel="stylesheet" href="/rest.css" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="/rest.css"></noscript>The media="print" trick works because the browser fetches print stylesheets without blocking rendering; the onload then promotes it to all media. Ugly, effective, and widely used.
Other options:
- Split by media query.
<link media="(min-width: 1024px)">doesn’t block rendering on mobile - Cut unused CSS. DevTools’ Coverage panel shows how much of each stylesheet the page actually uses — on a themed template it’s frequently under 20%
- Cascade layers and component-scoped CSS reduce the amount that has to be global in the first place — CSS Architecture
The tradeoff on inlining: critical CSS isn’t cached separately, so it’s re-sent on every page. For a few kilobytes that’s the right trade; for 60KB it isn’t.
Fixing JavaScript
Simpler: nothing in <head> should block parsing.
<script src="/app.js" defer></script> <!-- ordered, after parse -->
<script src="/analytics.js" async></script> <!-- independent, any order -->defer is the safe default — it preserves execution order and runs before DOMContentLoaded. async is only for genuinely independent scripts, because its order depends on download timing and produces races that pass locally.
Inline scripts always block, regardless of position, and a tag manager snippet is an inline script. See Tag Manager Performance.
Fonts
Not render-blocking themselves, but they block text rendering, which is often what LCP measures.
<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>With font-display: swap, text renders immediately in a fallback and swaps when the font arrives — trading a flash of unstyled text for not having invisible text. See Font Loading.
What isn’t worth blocking for
A useful audit question: would a user notice if this arrived a second later? For nearly everything below the fold, and for every analytics, chat, review and personalisation script, the answer is no.
The exception that undoes everything: an anti-flicker snippet from a client-side testing tool deliberately hides the page until a third-party script loads. It is render-blocking by design, applies to every visitor whether or not a test is running, and is often the single largest blocker on a retail site — Flicker and Flash of Original Content.
Measuring the effect
- DevTools → Network, sorted by start time. Anything before first paint on the waterfall is on the path
- DevTools → Performance, look for the gap between navigation start and first paint
- Coverage panel for how much of each blocking file is actually used
- Field data afterwards. A lab improvement that doesn’t show up in Real User Monitoring didn’t help real users — Field vs Lab Data