The following is a guest post by Wladston Ferreira Filho. We’ve covered critical CSS before, but the technique covered was specific to SCSS. Here Wladston covers the basics and introduces another way to do it, through PostCSS, and shows an example (with data) of the result.
Critical CSS is an effective, but all-too-rarely used approach for improving page rendering performance. Critical CSS is two things together:
- Deferred loading of the main stylesheet
- Inlining the most vital (“above the fold”) styles
Implementing Critical CSS requires some work. For one, it means splitting the CSS in two (the critical parts and the rest) which can be a maintenance problem.
Later in this article we’ll look at a tool to counter this drawback by splitting CSS automatically based on annotations (i.e. /* comments */
) in the main CSS file.
The Facts
Research by Mozilla, Akamai and many other sources confirm: small changes in page render time can significantly alter performance metrics. Under a bad network connection, page performance becomes even more important, as download times can be multiple times higher.
Google even provides a service to give pages a “speed score”, a not-so-subtle hint that performance can be related to SEO. Properly using Critical CSS is advised by Google to up your score. The technique is certain to cause a positive effect on render speed. The reduction in render time depends on how small you can make your Critical CSS, and how big your main stylesheet.
How Critical CSS Works
The “normal” approach to CSS is to include your main stylesheet as a <link>
in the <head>
. The downloading and parsing of that blocks rendering. Critical CSS makes your page render faster by bypassing that blocking.
The first step is to “inline” (a streamlined <style>
tag in the <head>
) the essential CSS required to render the page’s above-the-fold content. That enables the second step: non-critical CSS can be loaded asynchronously (non-blocking), while the web page is rendering. Once the big CSS file arrives, it’s appended to the page by JavaScript.
A common way to make this work in practice is by using a CSS preprocessor. The preprocessor can detect specially authored comments on the CSS, so it can distinguish the critical CSS, and separate it automatically. This has been covered on CSS-Tricks before, using SCSS. Let’s explore doing it in the native CSS syntax.
Heads up: to make this work you’ll need a server-side tool to inline the critical CSS into all pages you serve as well as add a few lines of inline JavaScript to load the main (non-critical) stylesheet.
Existing techniques for Critical CSS
Critical CSS ultimately requires having two separate pieces of CSS: critical and non-critical. How do we get them?
Full Manual: Maintain Two CSS files
With this strategy you directly edit two CSS files instead of one. While this strategy is simple and requires no tooling, it is way harder to work with. It is harder to understand, read, and change styles. It’s recommended only for static CSS that is unlikely to ever change.
Full Automation
Server side tools (such as Google Page Speed Extension) will automatically detect which of your CSS is required to render the above-the-fold content, and they will separate what they elect as critical CSS and inline it for you, without your interference.
This technique has some drawbacks: your automatically generated non-critical CSS is likely to change for each page evaluated, reducing the efficiency of CSS caching. It also doesn’t detect the Critical CSS flawlessly, particularly for small screens.
Moreover, you have no way to customize or fine tune the process.
SCSS with jacket plugin
If you use SCSS, you can install the Jacket plugin (details here). Jacket separates CSS marked with a special critical class into another file, generating critical and non-critical css after processing the LCSS. The problem with this technique is that it ties you to SCSS. If you decide to stop using it, or if you want to change your preprocessing flavour, you’ll have additional work to adapt your critical CSS solution.
My Technique: PostCSS and PostCSS-Split
My technique relies on marking all your Critical CSS declarations with simple, plain CSS comment. Let’s consider this super simple HTML to illustrate:
<!DOCTYPE html>
<html lang="en">
<body>
<header>
<h1>I'm a big header</h1>
</header>
<p>I'm below the fold</p></body>
</body>
</html>
header > h1 {
/* !Critical */ margin: 300px;
}
p {
border: 1px dotted black;
}
The first step is marking the CSS rules that are required to render the above-the-fold content, by placing /* !Critical */
inside them.
To figure out on which declarations from your main stylesheet should be in your critical CSS, you can get suggestions from free services like this one.
Once you have your base CSS file with “critical” comments in place, install PostCSS-Split with npm
. You’ll have to install Node.js if you haven’t already. In a terminal, issue this command to install PostCSS-Split:
sudo npm install -g postcss-split
Then you can issue this command, passing your commented base CSS file to PostCSS-Split:
postcss-split base.css
Brand new base-critical.css
and base-non-critical.css
files will be created, based on your input file. The contents of `base-critical.css` are to be inserted in the <head>
in a <style>
tag.
As for loading `base-non-critical.css`, you can use an asynchronous CSS loader. For instance, add this before the </body>
tag (and change <your_css_url>
accordingly):
<script>
function lCss(u, m) {
var l = document.createElement('link');
l.rel = 'stylesheet';
l.type = 'text/css';
l.href = u;
l.media = m;
document.getElementsByTagName('head')[0].appendChild(l)
}
function dCss() {
lCss('<your_css_url>', 'all')
}
if (window.addEventListener) {
window.addEventListener('DOMContentLoaded', dCss, false)
} else {
window.onload=dCss
}
</script>
Potential Pitfalls of any Critical CSS Technique
When using any Critical CSS technique, you are likely to hit some problems. Let’s see how to face them:
Precedence
If you have multiple CSS rules with the same specificity, rules declared later will prevail over rules declared earlier.
Keep in mind that the CSS you designate as critical will change its location: it will be inline in your <head>
, meaning that it loads first and will be overridden by any CSS that loads later with selectors of the same specificity.
If you are having problem getting your correct CSS styles using Critical CSS this way, make sure your CSS isn’t order-dependant. If you get strange results, use a CSS inspector to help you fix your specificity problems.
FOUC
If your Critical CSS does not include every rule required to render all of the above-the-fold content, or if your user starts browsing the under-the-folder content before it the bulk of your CSS is loaded, you’ll experience the FOUC (Flash of Unstyled Content) effect.
When your non-critical CSS loads, the browser is going to change the styling of your page to apply the rules from the non-critical CSS. This “flash” of style change can be undesirable.
One possibility for alieviating this awkwardness is using CSS transition to smoothly change from non-styled to styled. During development, you can manually add a delay to the JavaScript code that injects your bulk CSS.
Including the Critical CSS in the HTML pages
You’ll need a tool to inject the Critical CSS to the <head>
of your HTML pages. If you are using a back end language like PHP, you can do that easily with an include()
statement (or the like).
<!DOCTYPE html>
<html lang="en">
<head>
...
<style>
<?php include_once("/path/to/base-critical.css"); ?>
</style>
...
If you are not dealing with the code directly (e.g. you are using a content management system such as WordPress), you can search for a configuration setting or plugin that will do this for you. In WordPress, you can add a “hook” to inline the contents of your CSS file into your final HTML.
Jeremy Keith has outlined a way with Grunt/Twig.
Is this really worth it?
To summarize…
These are the steps required to implement this technique:
- Identify and mark your Critical CSS in your main stylesheet.
- Include a task in your deploy routine to split the base CSS into two files.
- Include the extra JavaScript code to load your main stylesheet asynchronously.
- Implement a server side include feature to add your Critical CSS contents to each pages
<head>
.
Case Study: Real World Website with Critical CSS
I’ve programmed the website https://code.energy
such that it can serve pages with or without Critical CSS. It will use Critical CSS by default, unless a nocritical
query string is included (example, https://code.energy?nocritical
). Another way to disable Critical CSS is to pass a user-agent header that contains the string nocritical
.
With this in place, we can easily measure how Critical CSS affects the speed performance of this website using online tools such as webpagetest.org. Webpagetest easily allows running tests will a custom user-agent string. These are the results from the average of 5 experiments for each scenario:
Critical | Load Time | Start Render | Full Load | Speed Idx |
---|---|---|---|---|
x | 0.949s | 0.988s | 1.003s | 1036 |
✓ | 0.838s | 0.695s | 0.893s | 755 |
The most impressive difference is the “Start Render” time. By loading the CSS asynchronously, we can see that the browser is able to make more requests is parallel, as it starts parsing the HTML way earlier, as you can see here:

Conclusion
If you want the best possible performance for your website, you need a Critical CSS strategy. And by using PostCSS-Split you can get it with a small maintenance cost.
Am I the only one reading ‘!Critical’ as ‘not Critical’?
!Yep => Nope!
Ya, but that’s !important…
Alain, that’s the first time I think this way, even though I’m a coder. Pat got the idea I had in mind—that’s something !important :)
You can also use a custom regular expression in the PostCSS plugin code if you prefer.
In my Magento/Gulp workflow I’m using Penthouse (https://github.com/pocketjoso/penthouse) to generate the critical CSS, which I inject into HTML using Magento’s layout instructions. And then loading the full CSS with filamentgroup’s loadCSS (https://github.com/filamentgroup/loadCSS).
Luis, interesting. What’s your experience with Penthouse, does it elect critical CSS nicely? loadCSS seems to be a well crafted piece of javascript. The approach I suggest is way simpler, but it works flawlessly in the browsers I care about :)
I can’t understand: isn’t PHP supposed to work BEFORE a page is sent to the client? How can this trick accelerate the page loading even though PHP is waiting for the critical to be loaded before sending the page?
@Marcello, The PHP doesn’t actually wait for anything to be loaded. You use some sort of build process (such as PostCSS) to extract the critical CSS into a separate file, and then you use PHP to embed the contents of that critical file directly into the HTML that is generated so that the browser doesn’t need to make a separate network request.
Then you can use javascript to asynchronously load the rest of your css. (You don’t need to wait for the rest to load before you start this, just run the code right away and the browser will automatically download the file without blocking rendering of the rest of the page)
The PHP example is inserting the css file inside the tag so it will render said css so you would get something like:
This would output the critical css ‘inline’ on your header and then load your css via
<link>
It’s worth mentioning that Jacket is a Compass component – those of us who prefer libsass won’t be able to use it.
Yup Michael. That’s one of the reasons I decided to go with something that doesn’t depend, syntactically, on anything other than pure CSS.
I know this is a little pedantic, but I think it’s worth pointing out;
inline
styles are added to the elementwhat is referred to in this article is an internal stylesheet
Whoops… you are correct. I wonder if Chris will let me edit the post!
What this article is referring to is actually an embedded stylesheet, not an internal or inline stylesheet.
To handle FOUC we could use overflow:hidden at body and remove it once non-critical style is loaded. I liked the approach for splitting css with post-css-split.
Thanks
Kalpesh, thanks, I’m glad to know you liked it :)
Indeed, hiding the body until all the CSS loads is a widely used strategy for handling FOUC. I’m leaning more towards a strategy that will let users actually see the content as soon as possible. I like to imagine users might be on a very bad/slow connection, and try to make things work the best way possible in this scenario.
@Wladston Indeed, as I block all JS by default, I hate to have to open dev-tools to set display (or most often: opacity) to be able to even see the content.
You can split it easily in ONE file. Just use PHP as CSS file:
link rel=”stylesheet” type=”text/css” property=”stylesheet” media=”screen” href=”style.php?id=blog”
and for the rest id=bottom
BTW thanks for the posting “Why I don’t use CSS preprocessors”. I never understand the need for SASS, LESS etc. I use PHP for minifying – no need for an extra tool and that work-chain. So all I have to do to work on that one file, is change the syntax-coloring (bottom right in Sublime Text).
Best workflow I’ve seen so far on the subject, thx!!
Sorry to be dense, but the example here:
h1 { /*!CRITICAL*/ margin: 30px; }
is quite different from the plug-in author’s example:h1 /*!CRITICAL*/ { margin:30px; }
Hey Robin, I’m very happy to see you liked the post! Thanks!
The examples on the post-css-split documentation are outdated, when I changed the default regex match, I completely forgot to update the documentation… my bad :/
You are invited to send a pull request on post-css-split’s code to fix this :)
Critical Css is relevant regarding the rendering time of a single page.
But what if a user navigates a few pages on the same site?
For example: a site where the total css is about 30000 characters and the critical css is 6700 characters (22%).
if a user navigates more than one page,
– with critical css, the first page renders faster, but then, each page loads 22% more css (internal).
– without critical css, the full css loads and is cached on the first page, and each following page should render faster.
What do you think?
Hey fr, that’s an important tradeoff you are talking about. The thing is that adding a few more bytes in the main GET response has a negligible effect on load time, yet it a brutal effect on page rendering speed. So if you are concerned about long-term caching, you can reduce some of your critical css, until you reach a balance that you are happy with. That’s the whole point of my proposed approach: to let people easily implement critical css the way they like it.
Hey Wladston. Thanks for your answer. There is also another point where I see a (small) problem: The critical Css might not be the same for different pages of the same website. So I wonder how you would maintain different critical Css for different pages?
Yes, that is indeed hard. This technique assumes that you have just one page layout, or very similar page layouts that are able to share the exact same critical CSS. So I see two alternatives:
Use the same critical CSS for all the pages in your site
Enhance the post-css-split plugin in such a way that you also describe the page layout the specific css rule is critical for. For instance, this would apply for all Critical sections:
While this would apply only for a specific type of page layout:
Remember that by doing this, the non-critical CSS of the two pages will be different, so you loose a little bit of browser cache efficacy.
How do you handle this stuff on multiple pages? For instance, a webshop usually has different landing pages with different critical css. Auto generated css is not something you want due to caching of css (lots of subpages). But maintaining critical css for every page seems not very doable.
Hey Ramon! Like I just replied to @fr, you can either combine the critical css or extend the post-css-plugin to handle per-page tags.
I would start with marking the set of critical css required for all pages. If in the end pages are including a lot of unnecessary critical css, I would start to tag per-page css.
I’m happy to work with you by expanding the post-css-split plugin to support this use case.
Nice post! This technique is a little bit too manual for me (hence why I created Penthouse), but it should work well if you have a simple site following one single template.
Just one warning: extracting the critical css from the original css (splitting it into two files) is really only okay if you go for one critical css file/template for your whole site (as opposed to individual critical css files for individual pages). Otherwise you’ll end up with different “full css” files for each page (since you extracted different critical css), and you will no longer be able to cache the full css.
For these reasons I always advise to NOT extract critical css from the full css, but to instead leave it there, duplicated. The duplication doesn’t matter much as the full css should only load once (and then be cached), and the only part that’s duplicated is the critical css, which should be small. With a complete “full css” you also don’t have to worry about specificity anymore, since the rules in the full css file will trump the rules from the critical css.
Cheers
I totally agree, also this would support the idea of using cookies to determine server-side wether or not to put the critical CSS in the head for returning users who should have the full CSS in their browser´s cache (although I also read that this could impose difficulties as well…).
I agree about keeping full CSS with all styles, that’s simpler when using these semi-automatic methods.
We use SCSS for critical CSS having main stylesheet with all styles imported and loaded via JS. Then we have a common critical CSS (normalize, etc.) and separate critical stylesheets for each site template. In the templates we include (via PHP) common critical CSS and template specific critical CSS (if needed).
On home page we were able to save around 12kB of inline CSS coming from other pages. It can be important as inline styles add a lot to HTML weight and you might have a problem to get bellow 14kB (especially when using some inline SVG yet).
sudo npm install -g postcss-split
obviously the plugin´s name is spelled without a dash after the “post” in contrary to the command mentionend in the article.
I think the pre-parsers are rather clever, these days, to prioritise the CSS—but wouldn’t it still be a good idea to bring the internal style to the very top of the head (right below the charset or the title)?
Nice write-up, BTW :)
Also, if you page is small and all your markup and CSS combined and gzipped are below 14K (as is the case with code.energy), you might consider internalising all your CSS in the head. Of course, the browser will have to parse it all up-front, but if your stylesheet is small enough … I didn’t benchmark the speed diff—but you would stay fully styled even without JS :)
Your websites should be fully styled even with JS turned off because when using critical CSS and JS loading of main CSS you should normally link the main CSS in noscript element :)
Good point :)
If you’re going after every millisecond (which is what you do when loading stylesheet non-blocking), you should not search for all
head
elements in the whole DOM. Don’t dodocument.getElementsByTagName('head')[0]
; you use the reference you already have:document.head
.Related:
document.body
forbody
element (you knew this),document.documentElement
forhtml
root element.