Critical CSS and render-blocking resources
Inline the CSS needed for above-the-fold content and defer the rest. Render-blocking resources in <head> are the single biggest cause of slow first paint.
What it is
A render-blocking resource is one the browser must fetch and parse before it can paint pixels. By default, every <link rel="stylesheet"> and every synchronous <script> in <head> blocks rendering.
Critical CSS is the subset of your stylesheet needed to render the visible part of the page on first load. Inlined in a <style> tag in <head>, it lets the browser paint without waiting for the full stylesheet.
<head>
<style>/* ~14KB of styles for above-the-fold content */</style>
<link rel="preload" href="/site.css" as="style" onload="this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/site.css"></noscript>
</head>
Why it matters
A typical site stylesheet is 100–300KB. On a slow connection, fetching it adds 500ms+ before any pixel appears. That delay shows up directly as poor LCP and First Contentful Paint. Render-blocking JavaScript is even worse — it blocks both parsing and rendering.
Inlining the critical CSS removes one round-trip from the critical path. The non-critical CSS still loads, but in parallel with rendering rather than ahead of it.
How to implement
Identify above-the-fold styles. Tools such as critical, penthouse, and critters parse the rendered page and extract only the rules used in the initial viewport. Build-time generation is fine; per-page is better.
Inline in <head>. Keep the inlined CSS under ~14KB (one TCP slow-start window) so it fits in the first packet.
Load the rest asynchronously. The preload + onload swap shown above is the standard pattern. A <noscript> fallback covers browsers with JavaScript disabled.
Move JavaScript out of the critical path. Add defer or type="module" to script tags. Both fetch in parallel and execute after parsing:
<script src="/app.js" defer></script>
<script type="module" src="/app.mjs"></script>
Use async only for truly independent third-party scripts.
Don’t block on third-party CSS. Webfont providers, analytics dashboards, and chat widgets that drop a stylesheet in <head> add an unknown origin to your critical path. Self-host or load asynchronously.
Watch the media attribute. <link rel="stylesheet" media="print"> does not block rendering. Useful for splitting print styles out.
Common mistakes
- Inlining the entire stylesheet. The HTML balloons; nothing is cacheable across pages.
- Forgetting the
<noscript>fallback. Users with JS off see an unstyled page. - Loading a synchronous
<script>tag in<head>for analytics. Now analytics blocks your LCP. - Critical CSS generated once for the homepage and reused everywhere. Article pages and product pages need different critical styles.
- Mistaking
asyncfordefer.asynccan execute mid-parse and block rendering.
Verification
- DevTools → Performance → record a load. The “Render-Blocking” badge marks every blocking request.
- Lighthouse “Eliminate render-blocking resources” lists them with estimated savings.
- View source: every non-critical
<link rel="stylesheet">should be deferred; every<script>in<head>should havedeferorasync.
Related topics
Sources & further reading
- web.dev — Extract critical CSS — web.dev
- web.dev — Defer non-critical CSS — web.dev
- MDN — Render-blocking resources — MDN
- web.dev — Eliminate render-blocking resources — web.dev