Critical CSS and Server-side Applications

A Workflow for non-static Website Performance.

The Critical Rendering Path: You’ve probably heard about it if you’re living in the front-end world. Against the do not mix HTML and CSS philosophy, it’s highly effective to put the render-critical parts of your stylesheet directly into the delivered HTML response. 1

But this article is not a pro and cons list of this technique — which will be deprecated if HTTP/2 is ready, rather a solution for one fundamental problem of the available tools: They are supposed to be used with static, highly predictable sites where deliverable HTML (generated by Jekyll or Middleman for example) files lying around, waiting to be analyzed.

Usually we build applications which are not static, but dynamic at a larger scale with varying layouts and templates, though. To extract the render critical CSS in those cases, we need to send actual HTTP Requests to evaluate what is critical on the pages in question.

Render Critical CSS Workflow / Overview

The Solution

We need three things in order to deliver the critical CSS based on the requested page:

  1. A build task which generates the critical CSS.
  2. Logic to find the page-matching critical CSS file.
  3. Output the critical CSS inline in the <head> section.

I skip the details on how your development environment might look like and go straight to the important parts, because the workflow is adaptable to almost any technology-stack. In my example, I’m using a WordPress instance served by Vagrant which resembles the later production environment.

 

The Build Task in gulp.js

Let’s take a look on how we generate the critical CSS.
We create a JavaScript array with an object for each page we want later to be generated, providing the localhost (or alias like in my sample code) url as well as a unique name.

In the gulp task we use JavaScript’s map() method on the previously defined array to iterate over all objects. In the function’s body we use the Penthouse Node module to generate the actual critical CSS by supplying the main CSS file and page data — Penthouse uses PhantomJS behind the scenes, a headless WebKit browser. The returned data is written to a file matching the objects name value.
When finished developing, run gulp criticalCSS to generate the CSS files for your production environment.

var penthouse = require('penthouse');
...
var criticalPages = [{
 url: 'http://local.dev/',
 name: 'index'
}, {
 url: 'http://local.dev/projects',
 name: 'projects'
}, {
 url: 'http://local.dev/about',
 name: 'about'
}];

gulp.task('criticalCSS', function() {
 criticalPages.map(function(page) {
  penthouse({
   url: page.url,
   css: 'dist/css/main.css',
   width: 1280,
   height: 1280
  }, function(err, data) {
   require('fs').writeFile('dist/css/critical/' + page.name + '.css', data);
  });
 });
});

 

The Function Logic

Now the defined directory contains our critical CSS files we can work with. On the Back-End side of our application we need some sort of logic to define which of the available files need to be inlined in the requested page. This part depends heavily on your application, so I go ahead and do WordPress and PHP things for now.

function criticalCSS()
{
    $dir = get_template_directory_uri().'/dist/css/critical/';
    $page = null;

    if (is_page('about')) {
        $page = file_get_contents($dir.'about.css');
    }

    elseif (is_page('projects')) {
        $page = file_get_contents($dir.'projects.css');
    }

    else {
        $page = file_get_contents($dir.'index.css');
    }

    $output = $page;

    printf('<style>%s</style>', $output);
} 

The example above is heavily simplified for readability reasons, but you get the point. We check which page (layout, type, …) is requested and return the corresponding stylesheet.

 

Inline and asynchronous load of CSS

Finally, we got the critical CSS files and the logic to know what we need to inline on a requested page. Time to inline the critical CSS and set up everything to load the big evil main CSS file asynchronously via JavaScript. Again, that part depends on your app, I assume a template called base or head.

<head>
...

<?php criticalCSS(); ?>  // function call to inline the critical CSS

<script>
 loadCSS( ... ){ ... }  // include loadCSS here...
 loadCSS("PATH/TO/main.css");  // load main.css async
</script>
<noscript>
 <link href="PATH/TO/main.css" rel="stylesheet">
</noscript>
...
</head>

The criticalCSS() function call handles all the things needed in order to output the complete <style>...</style> tag. The included loadCSS function handles the asynchronous load and prevents the render blocking, when called with the main CSS file.

 


 

But there is one trade-off to this method: We deliver the extracted critical CSS twice, in the HTML response as well as in the full CSS file which is asynchronously loaded through JavaScript.2 Depending on your sites structure and heaviness this caveat is more or less wasting a couple of bytes.

I think this downside is worth the initial render-improvement, especially if your main CSS file is large. The performance gain is exponential to the size of the main CSS file.

 


 

There are many additional steps someone can do to improve the performance of a site even further, I point to some things which can be considered.

Further Reading &
Additional Resources

Enhance.js – A boilerplate which contains many things for progressive enhancement.
Server Setup with nginx, HHVM and WordPress – How to deliver WordPress sites in an efficient and fast way, written by me.
Need for Speed 2 – Summary of many details and techniques regarding performance.

Questions, Comments or something else? Hit me up on Twitter.


  1. You can read more about the fundamentals here: Understanding Critical CSS
  2. The only way to avoid such duplication is to generate corresponding main CSS files with the content of the critical CSS file extracted. But then there is another problem: Caching. We have to deliver the main CSS file based on the current page, which would be otherwise cached directly after the asynchronous load. That’s a thing we want to avoid. 
Artikel