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.
But this article is not a pro and cons list of this technique, 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.
The Solution
We need three things in order to deliver the critical CSS based on the requested page:
- A build task which generates the critical CSS.
- Logic to find the page-matching critical CSS file.
- 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.
This article was published in 2015, some information may be outdated. Double-check the following instructions with current best practices in mind.
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.
1var penthouse = require('penthouse');2...3var criticalPages = [{4 url: 'http://demo.local/',5 name: 'index'6}, {7 url: 'http://demo.local/projects',8 name: 'projects'9}, {10 url: 'http://demo.local/about',11 name: 'about'12}];1314gulp.task('criticalCSS', function() {15 criticalPages.map(function(page) {16 penthouse({17 url: page.url,18 css: 'dist/css/main.css',19 width: 1280,20 height: 128021 }, function(err, data) {22 require('fs').writeFile('dist/css/critical/' + page.name + '.css', data);23 });24 });25});
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.
1function criticalCSS()2{3 $dir = get_template_directory_uri().'/dist/css/critical/';4 $page = null;56 if (is_page('about')) {7 $page = file_get_contents($dir.'about.css');8 }910 elseif (is_page('projects')) {11 $page = file_get_contents($dir.'projects.css');12 }1314 else {15 $page = file_get_contents($dir.'index.css');16 }1718 $output = $page;1920 printf('<style>%s</style>', $output);21}
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
.
1<head>2 ...34 <?php criticalCSS(); ?>5 // function call to inline the critical CSS67 <script>8 loadCSS( ... ){ ... } // include loadCSS here...9 loadCSS("PATH/TO/main.css"); // load main.css async10 </script>11 <noscript>12 <link href="PATH/TO/main.css" rel="stylesheet" />13 </noscript>14 ...15</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. 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.