When the browser parses HTML and encounters certain resources — most CSS files and synchronous JavaScript — it has to stop and download them before continuing. This is called "render-blocking", and it's one of the largest controllable factors in how fast a page first paints. A single render-blocking script in the head can add 500-1500ms to LCP.
Modern frameworks and CDNs have made this much easier to handle than it was a decade ago, but the patterns that cause render-blocking still recur — especially around third-party scripts, web fonts, and "we'll just include a CSS file in the head" templates.
What Counts as Render-Blocking
The browser blocks rendering on:
- External CSS files in the head (
<link rel="stylesheet">) without a media query that excludes the current device. - Synchronous external JavaScript in the head (
<script src="...">without async or defer). - Inline scripts in the head that run before the body (rare but slow if heavy).
- Web fonts referenced from CSS (kind of — they block text render but not initial paint, depending on font-display).
The browser does NOT block rendering on:
- Async or defer scripts.
- CSS in the body (technically — but causes layout shifts, so don't).
- CSS with
media="print"until print is invoked. - Images, video, fonts (they delay specific content, not the whole page).
Async vs Defer
Two attributes that change script loading behaviour:
async
<script src="analytics.js" async></script>
Downloads in parallel with HTML parsing. Executes as soon as it's downloaded, even if the rest of the HTML hasn't been parsed yet — which means the script can run before the DOM is fully ready, and order between multiple async scripts is not guaranteed.
Use for independent scripts that don't depend on the DOM or each other: analytics pixels, third-party widgets, social embeds.
defer
<script src="app.js" defer></script>
Downloads in parallel with HTML parsing. Executes after HTML is fully parsed but before DOMContentLoaded. Multiple defer scripts execute in the order they appear in the HTML.
Use for application logic that needs the DOM or has dependencies between scripts: framework code, page initialisation, anything you used to put at the end of the body.
Modules
<script src="app.js" type="module"></script>
ES modules are deferred by default — same behaviour as defer without needing to specify it.
The Default Should Be Deferred
For most scripts, the right answer is defer:
<script src="/dist/app.js" defer></script>
Let the browser parse the HTML in parallel. Run the script after parsing. This single change eliminates most render-blocking on most sites.
Common pushback: "but my script needs to run before the page is interactive." It will — defer scripts run before DOMContentLoaded, which is the standard signal for "page is ready". Unless you have a specific reason to block rendering, defer is right.
CSS: Critical and Non-Critical
CSS is render-blocking by default. The browser won't paint the page until all stylesheets in the head have loaded, because it doesn't know which styles affect what's visible.
The technique to unblock CSS is "critical CSS":
- Identify the CSS needed for above-the-fold content — typically 5-15KB of styles for the header, hero, and visible navigation.
- Inline that CSS in the head as a
<style>block. - Load the rest of your CSS asynchronously, typically with the print-trick:
<link rel="stylesheet" href="/main.css" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="/main.css"></noscript>
The browser doesn't block rendering on print stylesheets. Once the file loads, JavaScript flips the media to "all" and the styles apply.
For frameworks: Next.js, Nuxt, SvelteKit, and Astro extract critical CSS automatically. WordPress sites can use a critical CSS plugin or service like Critical or critters. Manual extraction is a pain; use tooling.
Web Fonts and Render-Blocking
Web fonts are tricky. The CSS that defines them isn't render-blocking, but the font files themselves block text render until they load (or until the browser's "font-display timeout" expires).
Mitigations:
font-display: swap— show fallback font immediately; swap when custom font loads. Acceptable for most sites.- Preload critical fonts:
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin> - Self-host fonts rather than Google Fonts — saves a DNS lookup and TLS handshake; fonts arrive faster.
- Use system fonts for content that doesn't need brand consistency.
font-family: system-ui, -apple-system, sans-serifuses the user's OS fonts; zero font load time.
Third-Party Scripts: The Quiet Killers
The render-blocking that matters most isn't usually your own code — it's the third-party scripts you've added over years:
- Analytics SDK (50-200KB).
- A/B testing tool (100-300KB).
- Customer support widget (200-500KB).
- Marketing pixels (Facebook, Google Ads, LinkedIn — 30-100KB each).
- Heatmap tools (Hotjar, FullStory — 100-300KB).
- Personalisation tools.
Each one, individually, is "small". Together they're often 1-2MB of script downloaded and executed on every page load. Fixes:
- Audit ruthlessly. Is each script earning its weight? Many were added years ago and the team that "needed" them is long gone.
- Use a tag manager (GTM) to consolidate. One async script loads everything else; better than 8 sync scripts.
- Defer heavy scripts until after first interaction. Customer support widgets in particular: load on hover or click, not on page load.
- Self-host where possible. Some analytics tools support self-hosting their tracker — eliminates the third-party DNS lookup and TLS handshake.
- Use Partytown or similar to run third-party scripts in a Web Worker, off the main thread. Significant INP improvement.
Identification
Several tools surface render-blocking resources:
- Site Speed Check reports render-blocking CSS and JS with size and load time.
- Lighthouse has dedicated audits for "Eliminate render-blocking resources" with potential savings estimates.
- Chrome DevTools Performance panel shows the critical render path — what loads when, and what's blocking what.
- Coverage panel in DevTools shows what percentage of each CSS/JS file is actually used. Identifies bloat.
The Practical Sequence
- Audit third-party scripts. Remove what you don't need; defer what you do.
- Add
deferto your own scripts in the head. - Inline critical CSS; async-load the rest.
- Add
font-display: swapto web fonts. - Preload critical fonts and the LCP image.
- Re-test with Site Speed Check.
Each step independently improves first paint. Done together, the impact compounds — sites that were taking 3-5 seconds to first paint can drop to under 1 second after a focused render-blocking audit.
What Doesn't Need to Be Optimised
The HTML itself. The image and video files. Async scripts. Properly deferred scripts. Print stylesheets. Anything below the fold. The browser handles all of these efficiently without intervention.
The 80/20 of render-blocking optimisation is: defer your scripts, extract critical CSS, audit your third-party junk. Three changes, often visible improvement within a day. Site Speed Check measures the before and after.