Annotating Your (Critical) CSS

Avatar of Wladston Viana Ferreira Filho
Wladston Viana Ferreira Filho on (Updated on )

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:

  1. Deferred loading of the main stylesheet
  2. 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.