Back to Performance
Performance
easy
mid

What is critical CSS extraction and why does it matter for performance?

Critical CSS is the minimal set of styles needed to render above-the-fold content. You inline it directly in <head> so first paint doesn't wait on a network round-trip for the full stylesheet, then load the rest of the CSS asynchronously. Tools (Critical, Penthouse, framework plugins) extract it per route; the trade-off is build complexity and keeping it in sync.

5 min read·~8 min to think through

Critical CSS is the subset of your stylesheet required to render above-the-fold content — what the user sees before scrolling.

The problem it solves

CSS is render-blocking: the browser won't paint until it has the CSSOM. If your stylesheet is one big file, First Contentful Paint waits for the entire download — a full network round-trip — even though only a fraction of those rules affect the initial view.

The technique

  1. Extract the rules that apply to above-the-fold elements (for a given viewport/route).
  2. Inline that critical CSS directly in a <style> block in the <head> — zero network cost, available immediately.
  3. Load the full stylesheet asynchronously so it doesn't block the first paint:

``html <style>/* critical CSS inlined here */</style> <link rel="preload" href="/styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'"> <noscript><link rel="stylesheet" href="/styles.css"></noscript> ``

Result: the browser paints the visible content almost immediately, then the rest of the styles arrive and apply for below-the-fold and interactive states.

How it's extracted

  • Tools like critical, penthouse, critters render the page in a headless browser at a target viewport and collect the CSS rules that matched visible elements.
  • Framework integration — Next.js (via critters/beasties), Nuxt, Astro, and SSR setups can do this per route at build or request time.
  • Run it per route/template, since each page's above-the-fold content differs.

Trade-offs

  • ✅ Big win for FCP/LCP, especially on slow networks and first visits.
  • Build complexity — extraction must run in the pipeline and be re-run when styles change, or it drifts out of sync.
  • ❌ Inlined CSS isn't cached separately — it ships with every HTML response (mitigated by HTML compression; usually small).
  • ❌ Getting the "fold" right is fuzzy across device sizes — tools target a viewport, so pick a representative one or a few.

When it matters most

First-paint-sensitive pages: landing pages, marketing sites, content sites, anything where bounce rate or Core Web Vitals matter. For app shells behind a login it matters less.

Senior framing

The senior answer states the why crisply — CSS is render-blocking, so don't make FCP wait for CSS the user can't even see yet — and is honest about the cost: it's a build-pipeline concern that drifts if not automated, and it's per-route. Mentioning that modern frameworks automate it (so you rarely hand-roll it now) shows current awareness.

Follow-up questions

  • Why is the full stylesheet still loaded after the critical CSS?
  • What are the downsides of inlining CSS?
  • How do you keep extracted critical CSS in sync with the real styles?

Common mistakes

  • Extracting once and never re-running it as styles change.
  • Using one critical-CSS bundle for every route.
  • Inlining too much, bloating the HTML.
  • Forgetting the <noscript> fallback for the async load.

Edge cases

  • The 'fold' varies by device — extraction targets a chosen viewport.
  • Inlined CSS isn't independently cacheable across pages.
  • Dynamic/interactive-state CSS isn't 'critical' and can load later.

Real-world examples

  • Landing pages, e-commerce home/category pages, content/news sites optimizing LCP.

Related questions