
⚡ Critical CSS: what it is, how to generate it, and speed up your website
Your page takes 3 seconds to load, and visitors leave. You open Google PageSpeed Insights, and there it is in red: "Eliminate render-blocking CSS." Sound familiar? According to HTTP Archive data for 2025, the median page loads 7 external stylesheets, and average CSS weight has exceeded 120 KB. Every file in <head> is a barrier between the user and content.
The problem isn't "heavy" CSS. The browser won't render a single pixel until it downloads and parses ALL styles. Even the ones styling a footer at pixel 4000. The solution is Critical CSS: a technique that extracts from your full stylesheet only what's needed for the visible viewport and embeds it directly in <head>. The rest loads asynchronously and blocks nothing.
We implemented critical CSS across all techblog projects and built a working workflow: from manual generation in 30 seconds to full automation via Gulp and WordPress plugins. Below is a step-by-step guide with no fluff.
💡 Quick overview:
- Understand render-blocking mechanics and why the browser waits for CSS even when HTML is ready
- Choose the right tool: a free online generator in 10 seconds, Penthouse for npm projects, or WP Rocket / Autoptimize combo for WordPress
- Set up a Gulp workflow for automatic critical CSS regeneration on every deploy
- Avoid common pitfalls: FOUC, JS content, media query issues
What critical CSS is and why the browser slows down
The browser builds two structures: DOM from HTML and CSSOM from styles. Until CSSOM is ready, rendering pauses. This is render-blocking CSS: the page stays blank until all stylesheets in <head> load.
Google PageSpeed Insights diagnoses the problem directly:
Your page has 3 blocking CSS resources. This causes a delay in rendering. None of the content above the fold can be rendered without waiting for the following resources to load.
The term "above the fold" comes from newspapers. On the web, it means the visible area before scrolling: roughly 600 px on desktop, 900 px on laptops, 400 px on smartphones. Most developers target safe values of 1300 px width and 900 px height.
Critical CSS is the minimum set of styles needed to render just that area. Grid, navigation, fonts, hero section. Everything else is deferred and loads asynchronously: the page appears instantly while additional styles load in the background.
The numbers are significant. According to Google's data, inlining critical CSS reduces First Contentful Paint by 0.5-1.2 seconds on mobile devices. For e-commerce, this directly impacts conversion: research by Portent showed that sites with FCP under 1 second convert 3 times better than sites with 3-second FCP.
Generation tools: from free online to commercial API
Generating critical CSS manually is impossible. You need a tool that renders the page in a headless browser and extracts only the used styles. Here are three working options.
Tool | Type | Cost | Automation | Best for |
|---|---|---|---|---|
Critical Path CSS Generator | Online | Free | No | Single pages, getting started |
Penthouse | npm package | Free | Yes, via Gulp/scripts | Developers, batch processing |
criticalcss.com | SaaS | From $10/month | Yes, API + WordPress | Production, large sites |
Critical Path CSS Generator: online, in 10 seconds
Critical Path CSS Generator by Jonas Ohlsson is a free tool worth starting with. The interface is simple: paste the page URL, paste your FULL CSS (even 200 KB minified), click the button, get styles for above-the-fold.
Pros: zero barrier to entry. No Node.js, npm, or scripts needed. Cons: purely manual work. You process each page separately, and when styles change, you repeat everything. Works for a 5-page site, but 50 pages becomes painful.
The generator's creator later launched the commercial criticalcss.com service, an evolution of the same idea with auto-updates, screenshot validation, and an API for batch processing.
Penthouse: command-line generator for Gulp
Penthouse (2680+ stars on GitHub) is an npm package from the same author. Under the hood: Puppeteer and headless Chromium. It opens the page, determines used CSS for a given viewport, and discards the rest.
Installation:
1 npm install --save-dev penthouse
Minimal call from a Node script:
1 const penthouse = require('penthouse'); 2 const fs = require('fs'); 3 4 penthouse({ 5 url: 'https://example.com', 6 cssString: fs.readFileSync('./style.css', 'utf8'), 7 width: 1300, 8 height: 900 9 }).then(criticalCss => { 10 fs.writeFileSync('./critical.css', criticalCss); 11 });
Width 1300 and height 900 are empirically safe values. Media queries wider than the critical viewport are stripped by default. To keep them, use the flag keepLargerMediaQueries: true.
Important note: Penthouse runs with JavaScript disabled. If above-the-fold content is generated by a JS framework, the HTML before hydration is empty, and Penthouse won't find any styles. The solution: server-side rendering (SSR) or the forceInclude parameter with selectors to preserve forcefully.
WP Rocket and Autoptimize: two WordPress plugins
If your site runs on WordPress, manual critical CSS generation is unnecessary hassle. Many pages, multiplying templates, changing styles. Two plugins solve the task completely.
WP Rocket is a premium plugin (€59/year for 1 site, €119 for 3, €299 for 50). The built-in "Remove Unused CSS" option automatically generates critical CSS for each page, embeds it in <head>, and loads the rest asynchronously. Two checkboxes, and the "Eliminate render-blocking resources" recommendation is resolved. Bonus: the built-in Rocket Insights hub powered by GTmetrix displays metrics right from the WordPress admin.
Autoptimize is a free plugin with an "Inline and Defer CSS" option. The basic version generates ONE critical CSS file for the entire site, which doesn't work in practice (different pages, different above-the-fold content). The solution: integration with criticalcss.com service (WordPress plan, from $10/month). The plugin automatically requests critical CSS via API, embeds it, and updates when changes occur.
For most WordPress users, the WP Rocket or Autoptimize + criticalcss.com combo solves the problem completely. The rest of this article is for those building custom sites.

Manual workflow: generation, insertion, verification
If you're building a site without a CMS, the process fits into four steps.
Step 1. Copy ALL your CSS. Combine all site styles into ONE file. If you write Sass, compile it. If using a framework like Bootstrap or Tailwind, include its CSS completely. Don't guess what's "probably not needed"; the generator will figure it out.
Step 2. Generate critical CSS via the online generator or Penthouse. The output is a minified block of styles responsible only for above-the-fold.
Step 3. Insert critical CSS into a <style> tag inside <head>. This is crucial: inline styles create no additional HTTP requests and are processed by the browser immediately. The result looks something like this:
1 <!DOCTYPE html> 2 <html lang="ru"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <style>@charset "UTF-8";*,*::before,*::after{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15}body{margin:0;font-family:'Inter',Helvetica,Arial,sans-serif;font-size:1rem;line-height:1.5;color:#222;background:#fff}nav{display:block}.container{max-width:1200px;margin:0 auto;padding:0 20px}</style> 7 <title>Page Title</title> 8 </head>
Step 4. Move external CSS and JS from <head> to the bottom of the page, before the closing </body>. Yes, <link rel="stylesheet"> in the footer is technically invalid per the spec, but it works in all browsers in practice and solves the blocking problem. The alternative is loadCSS from Filament Group for async style loading without moving to the footer.
Finally, verify through Google PageSpeed Insights. Did the "Eliminate render-blocking resources" recommendation disappear? You did everything right.
Gulp + Penthouse: automation for projects without WordPress
Every style change requires critical CSS regeneration. Doing this manually is mind-numbingly tedious. Gulp automates the process.
Our production workflow:
1 // gulpfile.js 2 const gulp = require('gulp'); 3 const penthouse = require('penthouse'); 4 const fs = require('fs'); 5 const urlList = require('./criticalcss-pagelist.json'); 6 7 gulp.task('criticalcss', function () { 8 urlList.urls.forEach(function (item) { 9 penthouse({ 10 url: item.link, 11 css: './css/style.min.css', 12 width: 1300, 13 height: 900, 14 keepLargerMediaQueries: false, 15 renderWaitTime: 100, 16 timeout: 30000 17 }).then(function (criticalCss) { 18 fs.writeFileSync(item.output, criticalCss); 19 console.log('✓ Critical CSS generated: ' + item.link); 20 }).catch(function (err) { 21 console.error('✗ Error for ' + item.link + ': ' + err.message); 22 }); 23 }); 24 });
The criticalcss-pagelist.json file with URL and output file list:
1 { 2 "urls": [ 3 { "link": "https://example.com", "output": "./critical/home.php" }, 4 { "link": "https://example.com/about", "output": "./critical/about.php" }, 5 { "link": "https://example.com/contact", "output": "./critical/contact.php" } 6 ] 7 }
We write output to PHP files that wrap the CSS and get inserted via include into the <head> of the corresponding template. Adapt to your stack: write to .css, insert via server-side include, whatever works.
Run: gulp criticalcss, and all pages are processed in one pass. Hook the task into CI/CD on deploy, and critical CSS stays current.
Problems you will encounter
Critical CSS is not without sharp edges. Here's what we caught in practice.
Flash of unstyled content (FOUC). Critical CSS didn't cover some element, and the user sees style jank when full styles load. Cause: content positioned outside the viewport via absolute or transform, and the generator missed it. Fix: the forceInclude flag with the problematic element's selector.
JS-generated content. Almost all generators (Penthouse, online tool) run with JavaScript disabled. If your landing page is built with React, Vue, or Angular without server-side rendering, the HTML is empty, and the generator has nothing to analyze. The only reliable solution: enable SSR at the framework level. The alternative is manually specifying selectors via forceInclude, but that's fragile.
Media queries. By default, Penthouse strips rules whose min-width exceeds the specified viewport. If you don't set keepLargerMediaQueries: true, desktop styles get lost, and the site breaks on wide screens. Always verify results at actual resolutions.
Caching. You generated critical CSS, deployed, changed the header style a week later, but the old version sits in <head>. Without CI/CD automation, you'll forget to regenerate. The Gulp task on deploy solves this completely.
⁉️🤔 Frequently asked questions
Is critical CSS mandatory for every site?
No, not every site. If your site has a single 20 KB CSS file and FCP is already under a second, the benefit will be negligible. But if
<head>contains 3-4 stylesheets totaling around 100 KB, critical CSS will deliver measurable improvement. For a simple landing page, the online generator takes 10 minutes. For a WordPress store, WP Rocket with a couple checkboxes does it. Check PageSpeed Insights: if "Eliminate render-blocking resources" is at the top, do it.
Can I just move all <link> tags to the footer and skip the hassle?
Technically no. The HTML spec requires
<link rel="stylesheet">only in<head>. In practice, browsers render pages with<link>in the footer, but you get a flash of unstyled content (FOUC) until styles load. Acceptable for a simple business card site, not for a commercial project. The right approach: critical CSS inlined in<head>, async loading for the rest vialoadCSSormedia="print"withonload="this.media='all'". Moving to the footer is a workaround that works at the cost of visual jank.
What changed with HTTP/3 and Early Hints?
HTTP/3 and 103 Early Hints let the browser start loading critical resources before receiving the full server response. This reduces latency but doesn't eliminate render-blocking: styles still need to download and parse. Early Hints and critical CSS work together, not instead of each other: Early Hints speeds delivery, critical CSS speeds first render. The two techniques reinforce each other.
Penthouse hasn't been updated in 4 years. Is it still relevant?
Penthouse's last release (v2.3.3) came out in 2022, but the package remains functional: it runs on Puppeteer and Chromium, which are stable. The core functionality (generating used CSS for a given viewport) doesn't require frequent updates. An alternative is Critical by Addy Osmani (12,000+ stars), which gets more active updates and also integrates with Gulp/Grunt.
How do I handle dynamic pages where content depends on the user?
For pages with personalization (user dashboard, cart, admin panel), critical CSS is generated not for a specific user but for the general structure: header, sidebar, grid. Dynamic content goes into the async portion. If styles for a user-specific above-the-fold block are essential, add the corresponding selectors via
forceInclude. Generate critical CSS based on the page's general structure, and leave personalized blocks for async loading withforceIncludefor mission-critical elements.
Should you implement critical CSS on your project
If your site loads more than one CSS file in <head>, critical CSS will deliver measurable speed gains. For a three-page landing, the online generator takes 10 minutes. For a WordPress blog or store, two checkboxes in WP Rocket. For a custom project, an hour setting up Gulp and Penthouse pays off on the first deploy.
The barrier to entry is lower than it seems. Take the free online generator, copy your page's CSS, paste it, and in 30 seconds you'll see results. When you want automation, come back to the Gulp workflow section: it solves the problem completely. Doubling load speed with a couple hours of work is a deal that's hard to refuse.



