Back to Blog
PerformanceJuly 2, 20264 min read

Optimizing Critical CSS to Slash FCP and Improve INP

Optimizing critical CSS is the fastest way to slash FCP and improve INP. Learn how to eliminate render-blocking resources and streamline your delivery.

web performancecritical cssfrontend developmentFCPINPweb vitalsCSSPerformance

When I first ran a Lighthouse audit on a legacy e-commerce site last month, the FCP (First Contentful Paint) was hovering around 2.4 seconds. The culprit was immediately obvious: a massive 150KB main stylesheet that the browser had to download, parse, and execute before it would render a single pixel.

If you’re struggling with similar performance bottlenecks, the fix isn't just "minifying" your files. You need a robust critical CSS delivery strategy. By inlining the styles required for the "above-the-fold" content and deferring the rest, you remove the primary render-blocking dependency that cripples your initial load times.

Why Render-Blocking Resources Kill Your Metrics

Browsers are notoriously cautious. When they encounter a <link rel="stylesheet"> tag in the <head>, they treat it as a render-blocking resource. The browser stops everything—parsing the HTML, executing scripts, and painting the page—until that file is fully downloaded and processed.

This behavior is a major contributor to poor FCP and, indirectly, impacts your INP (Interaction to Next Paint) by hogging the main thread during the initial load. When your main thread is busy calculating layout for a massive CSS file, it can't respond to early user interactions. If you’re looking to clean up your dependency graph, you should also look at how to eliminate critical request chains to ensure your browser isn't waiting on unnecessary assets.

The Strategy: Extract, Inline, and Defer

The goal is simple: get the styles for the header, hero section, and navigation into the HTML document itself. Everything else (footer styles, modal CSS, secondary page components) should load asynchronously.

We first tried an automated tool that scanned the entire site, but it ended up including too much "junk" CSS, bloating our HTML. Instead, I moved to a manual approach for the core layouts, which cut our critical payload by about 40%.

Step 1: Identify the Above-the-Fold Styles

Use the "Coverage" tab in Chrome DevTools to see which CSS rules are actually being used during the initial load. You’ll be surprised at how much dead code ships with your main bundle.

Step 2: Inline the Essentials

Take those critical rules and place them in a <style> block inside your <head>. This allows the browser to render the page immediately upon receiving the HTML.

Step 3: Defer the Rest

Load your remaining CSS files asynchronously using this pattern:

HTML
style="color:#808080"><style="color:#4EC9B0">link rel="preload" href="styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
style="color:#808080"><style="color:#4EC9B0">noscript>style="color:#808080"><style="color:#4EC9B0">link rel="stylesheet" href="styles.css">style="color:#808080"></style="color:#4EC9B0">noscript>

This trick tells the browser to download the file at a lower priority without blocking the initial render.

MethodImpact on FCPComplexityBest For
Standard CSSPoorLowSmall sites
Critical InliningExcellentHighLarge/Complex apps
CSS PreloadingGoodMediumPerformance-first sites

Connecting Performance to INP

While most people associate CSS with visuals, don't ignore the interaction cost. If your CSS is massive, the browser spends significant time on style recalculation and layout. If a user tries to click a button while this is happening, the interaction will be delayed, leading to a high INP score.

Once you've cleared the render-blocking CSS, you might still face issues with heavy third-party scripts. If you're still seeing high INP, check out my guide on how to stop third-party scripts from blocking to keep that main thread responsive.

Lessons Learned

We once tried to inline everything to get a perfect FCP score, but that backfired. Our HTML document size ballooned, which actually delayed the Time to First Byte (TTFB) because the server took longer to stream the response.

The sweet spot is usually around 10KB to 15KB of inlined CSS. Anything more, and you're just trading one bottleneck for another. Always monitor your network waterfall; if your HTML document is taking longer than 300ms to arrive, your inlining strategy is likely too aggressive.

If you're still seeing UI jitter or layout shifts after optimizing your styles, I've previously covered how to stop UI jitter by using CSS aspect ratios. Balancing these optimizations is an iterative process, and I’m still experimenting with different build-time extraction tools to keep the maintenance burden low. Don't aim for perfection on the first pass; start by identifying the biggest render-blocking offender and go from there.

FAQ

Does inlining CSS hurt caching?

Yes, it does. Because the CSS is inside the HTML, it can't be cached independently. That's why you only inline the critical portion and load the rest externally.

Will this fix all my Core Web Vitals?

It will significantly improve FCP and LCP. However, INP is often more dependent on JavaScript execution. If you still have high INP, you need to focus on architecting non-blocking DOM updates.

Should I use a tool to automate this?

If you have a large site, yes. Tools like critical or Penthouse can automate the extraction, but keep a close eye on the output to ensure they aren't including unnecessary overrides.

Similar Posts