WSS
Web Specification Studio Home
On this page
PerformanceRecommendedUpdated

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 async for defer. async can execute mid-parse and block rendering.

TCP Slow-Start Window Budget & Multi-Viewport Breakpoint Extraction

Optimizing critical CSS inlining requires balancing network transport limits with responsive layout requirements:

  • 14KB Initial Congestion Window (cwnd) Limit: On new TCP connections, the server transmits an initial burst of approximately 14KB (10 TCP packets of 1460 bytes payload). Keeping HTML headers + inlined <style> block under 14KB ensures the entire critical payload arrives in the initial TCP round-trip, rendering First Contentful Paint (FCP) immediately.
  • Responsive & Container Query Multi-Viewport Extraction: Critical CSS extraction tools (critters, penthouse) must be configured to evaluate multiple viewport dimensions simultaneously (e.g. mobile 375x667 and desktop 1440x900). Omitting mobile viewport rules causes layout shifts (CLS) when mobile devices render using desktop-only critical CSS.
  • Handling Font & Image Asset Preloading in CSS: Ensure background image URLs embedded inside critical CSS rules (hero backgrounds) carry corresponding <link rel="preload" as="image"> tags in <head> to prevent delayed discovery.

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 have defer or async.

Related topics

Sources & further reading