Brian Love
Angular + TypeScript Developer in Denver, CO

Generate Critical Path CSS with Node.js

Reading time ~8 minutes

In this post I will show you how to generate the critical path CSS to avoid render-blocking JavaScript and CSS.

If you have ever tested your website using Google PageSpeed Insights tool you might get an error indicating:

Google PageSpeed score

Eliminate render-blocking JavaScript (JS) and CSS in above-the-fold content

What does that mean?

Well, it means that there are either JS files or CSS files that are blocking the rendering of your page. When a user requests a page on your website the first request results in the html source. The browser parses that file, and then for each external script and external stylesheet it issues a request, waits, and parses the response. And, this all happens before the browser will complete the rendering of your page to the user.

While that may not be a big deal to you - it is to Google.

Critical Path

The critical path represents the steps that the browser takes before the page is rendered to the user. Our goal is to minimize the number of steps.

One way to do this is to load any external CSS stylesheets asynchronously after we have displayed (rendered) the page. This puts the task of downloading and parsing the CSS off until after the user has seen our page.

But, when we load our external CSS asynchronously we don’t want to display an un-styled page first. Right?

So, what we can do is this:

  1. Load external CSS asynchronously to prevent render-blocking.
  2. Determine the “critical” CSS that is needed for above-the-fold content.
  3. Inline that CSS, or put it in a <style> tag in the <head>.

I’m going to be using a Node.js module written by Jonas Ohlsson called penthouse. Kind of a funny name. But, it’s a great tool.

Image showing the fold of a webpage

Generating Critical PathPath

There is an automated way to generate the critical path CSS for a page on your website using the Node.js module called penthouse. According to the GitHub page for the project:

Penthouse is a tool generating critical path css for your web pages and web apps in order to speed up page rendering. Supply the tool with your site’s full CSS, and the page you want to create the critical CSS for, and it will return all the CSS needed to render the above the fold content of the page.

As a side note, you can also use penthouse as part of your Grunt build or use the free online tool.

Let’s walk through getting this set up:

  1. First, we’ll create a Node.js script for this.
  2. Next, we’ll get the skeleton for our script started by requiring the necessary node modules and creating a main() function that will return a promise object.
  3. Then, we’ll generate the critical path CSS for a single URL.

Here is a quick screen shot of the folder structure that I will be using in this article:

The sample application folder structure

Create Node.js Script

The first step is to create a Node.js script. This is very easy. First, let’s create the file and enable the execution of the file.

$ mkdir bin
$ cd bin
$ touch generate.js
$ chmod +x generate.js

Now, let’s add the following shebang at the top of the file:

#!/usr/bin/env node

That’s it. Now we are ready to start coding up our script.

Getting Setup

The first step is to install and require the penthouse Node module. I’m going to assume that you have already installed Node.js and the Node Package Manager (npm) that is included.

$ npm install penthouse --save-dev

And, now let’s require the module in our generate.js file.

//require penthouse package to generate critical css path
const Q = require("q");

I will be using Q node module for Promises in my script. This will enable us to keep track of when our process has completed and we can exit safely. If you are not familiar with Promises, you should check out promisejs.org.

$ npm install q --save-dev
//require penthouse package to generate critical css path
const Q = require("q");

Next, let’s require the path and fs (filesystem) node modules. And, we’ll also define some constants. First __baseDir will be the current directory that I am executing the script within. The __baseOutput constant will be the full path to the destination directory. For my project I have a css/critical folder where I am going to store the files.

You may ask yourself - if you are storing the critical path CSS in a .css file - what advantage is that? Well, I am going leave the full implementation up to you and your web application layer to read the critical css file, and to uglify and place it in a <style> tag in your <head>. This is beyond the scope of this article.

Finally, we’ll set up the default configuration for our penthouse requests. The configuration object is based on the documentation for the penthouse node module.

//require libraries
const path = require("path");
const fs = require("fs");
const __baseDir = "./";
const __baseOutput = path.resolve(__baseDir, "css/critical");
const __baseUrl = "http://brianflove.com/";

//default configuration for penthouse
var config = {
  css : path.resolve(__baseDir, "css/build/app.css"),
  height : 900,
  strict: false,
  timeout: 30000,
  width : 1300
};

Generating Critical Path CSS

With the setup out of the way you should have installed the q and penthouse modules via npm, and your generate.js file should look like this:

#!/usr/bin/env node

//require penthouse package to generate critical path css
const Q = require("q");

//require penthouse package to generate critical path css
const penthouse = require("penthouse");

//require libraries
const path = require("path");
const fs = require("fs");
const __baseDir = "./";
const __baseOutput = path.resolve(__baseDir, "css/critical");
const __baseUrl = "http://brianflove.com/";

//default configuration for penthouse
var config = {
  css : path.resolve(__baseDir, "css/build/app.css"),
  height : 900,
  strict: false,
  timeout: 30000,
  width : 1300
};

We can now start coding our main function that will be executed by Node.js. I like to use the main() convention from C. My main() function will return a promise object. Here is the skeleton for my main() function along with the code to invoke it.

/**
 * The main function to execute
 *
 * @method main
 * @return {Promise} The promise object
 */
var main = function() {
  //implementation goes here
  console.log("foo");

  //this is just for the skeleton, you will want to remove this when we flesh out the implementation
  return Q();
};

//let's do it!
if (require.main === module) {
  main().then(function() {
    process.exit(0);
  }).catch(function(error) {
    console.log(error);
    process.exit(1);
  });
}

With the main() method written we can test this by executing it at the command-line.

$ ./js/bin/generate.js

You should see a simple “foo” message and the node.js process should exit successfully.

Image showing the fold of a webpage

Note that we will remove all of the dummy code inside the main() method before we add the implementation code.

For our implementation, the first thing we should do is to create a simple function for generating the critical path CSS for a URL. The function should accept two parameters:

  1. url - the URL for the request
  2. output - the full path to the critical path css output

Let’s name the function generateCriticalCss(). It should return a promise object that is resolved when the penthouse callback function is called without an error, and rejected if the error argument is not null (as per the penthouse documentation).

/**
 * Generate the critical css
 *
 * @method generateCriticalCss
 * @param url {string} The url to retrieve
 * @param output {string} The file path to output the css to
 * @return {Promise} The promise object
 */
var generateCriticalCss = function(url, output) {
  //create promise
  var deferred = Q.defer();

  //configure url
  config.url = url;

  //execute penthouse request
  penthouse(config, function(error, criticalCss) {
    //check for an error
    if (error) {
      //log
      console.error(error);

      //reject promise
      return deferred.reject(new Error(error));
    }

    //log
    console.log("Critical path css saved to: %s.", output);

    //write critical css to output file
    fs.writeFileSync(output, criticalCss);

    //resolve promise
    deferred.resolve(1);
  });

  return deferred.promise;
};

Ok, now that we have our processing method defined let’s continue in our main() function. First, let’s make sure our output directory exists.

//ensure the base output directory exists
if (!fs.existsSync(__baseOutput)){
  fs.mkdirSync(__baseOutput);
  console.log("Creating output directory: %s", __baseOutput);
}

We’re finally ready to invoke our generateCriticalCss() method to create the critical path CSS for the index page. I am going to store the output in css/critical/index.css.

var output = path.resolve(__baseOutput, "index.css");
return generateCriticalCss(__baseUrl, output);

The complete generate.js file should now look like this:

#!/usr/bin/env node

//require penthouse package to generate critical css path
const Q = require("q");

//require penthouse package to generate critical css path
const penthouse = require("penthouse");

//require libraries
const path = require("path");
const fs = require("fs");
const __baseDir = "./";
const __baseOutput = path.resolve(__baseDir, "css/critical");
const __baseUrl = "http://brianflove.com/";

//default configuration for penthouse
var config = {
  css : path.resolve(__baseDir, "css/build/app.css"),
  height : 900,
  strict: false,
  timeout: 30000,
  width : 1300
};

//allow for lots of event listeners
process.setMaxListeners(0);

/**
 * The main function to execute
 *
 * @method main
 * @return {Promise} The promise object
 */
var main = function() {

  /**
   * Generate the critical css
   *
   * @method generateCriticalCss
   * @param url {string} The url to retrieve
   * @param output {string} The file path to output the css to
   * @return {Promise} The promise object
   */
  var generateCriticalCss = function(url, output) {
    //create promise
    var deferred = Q.defer();

    //configure url
    config.url = url;

    //execute penthouse request
    penthouse(config, function(error, criticalCss) {
      //check for an error
      if (error) {
        //log
        console.error(error);

        //reject promise
        return deferred.reject(new Error(error));
      }

      //log
      console.log("Critical path css saved to: %s.", output);

      //write critical css to output file
      fs.writeFileSync(output, criticalCss);

      //resolve promise
      deferred.resolve(1);
    });

    return deferred.promise;
  };

  //ensure the base output directory exists
  if (!fs.existsSync(__baseOutput)){
    fs.mkdirSync(__baseOutput);
    console.log("Creating output directory: %s", __baseOutput);
  }

  var output = path.resolve(__baseOutput, "index.css");
  return generateCriticalCss(__baseUrl, output);
};

//let's do it!
if (require.main === module) {
  //main();
  main().then(function() {
    process.exit(0);
  }).catch(function(error) {
    console.log(error);
    process.exit(1);
  });
}

While this example only shows how you might generate a the critical path css for a single page on your website, you can easily extend this to dynamically generate the critical css for your complete website. Best of luck on reaching 100/100 in your Google PageSpeed Insights score!

Brian Love

Hi, I'm Brian. I am interested in TypeScript, Angular and Node.js. I'm married to my best friend Bonnie, I live in Denver and I ski (a lot).