Inlining Critical CSS for Dynamic Web Apps

During the redesign I was faced with a problem and I had to rethink the way I inline CSS in my blog, so I figured it’d be a good time to write about it in more detail. Critical CSS inlining is a technique you can use to speed up the performance of your web apps by, well, inlining the “critical” CSS in a <style> tag, and deferring the rest of your CSS. How does that work?

First off, we have to identify the critical content of a page. In my case, what’s visible below constitutes the critical content, also known as the content that’s “above the fold”.

Redesign article above the fold

This is the piece of the site we want to get in the face of humans immediately. That’s not just limited to blogs or other content distribution sites, web apps should also strive to serve the content above the fold as fast as possible to their users, and inlining critical CSS is an excellent way of shaving milliseconds from your load time.

Now that we’ve identified the critical content of a page we can move onto the next step: identifying the bare minimum set of CSS rules that allow visible content to be rendered exactly as seen on the screenshot. There’s tools that can help automate that away (as we’ll see in a minute), so let’s move on. What’s next?

Well, once the critical CSS has been identified, it should be placed inline inside a <style> tag, as shown below (see also the source code for this web page).

What we just did means that the top of the muffin would be immediately readable to the user, without any extra request being made for a CSS file. That means that, unlike in the traditional approach on blocking on that extra CSS request before rendering any content, the content is displayed immediately. Problem is, if you haven’t removed the <link> tag pointing to the rest of your CSS, it’s still as slow as ever. If you did remove it, the rest of the page would be unstyled. To fix this, we simply defer loading of the <link> tag for later.

An endpoint such as http://localhost:3000/ – where your site is hosted, it can be development as long as layout matches roughly

A viewport size (which defaults to 1300x900) – everything within the viewport is considered critical content

With Node.js installed and a package.json ready, head over to the command-line, jump into your project directory, and enter the following command to install phantomjs and penthouse. The -D flag is an alias for --save-dev, i is short for install.

npm i phantomjs penthouse clean-css -D

With both those dependencies in your project, we can now get started. Let’s also add a script entry to our package.json. I typically place my build scripts inside a build directory, and name them by environment. I usually also have smaller scripts that I reuse across environments.

{
"scripts": {
"build-production": "build/build-production"}
}

With that out of the way, we can now create our script. Remember to make it executable so your OS won’t complain!

touch build/build-production
chmod +x build/build-production

Okay, okay. Finally we get to business, let’s look at the snippet below. Note how I’m calling phantomjs with the custom penthouse script and passing in the parameters that penthouse requires, namely an endpoint where they can visit your app, and a CSS file to analyze. Your app has to be running, naturally. Once that’s done we save the critical CSS into a file. I usually place files generated during builds into a .bin directory, but you can place them where you want.

Note that you probably want to automate the server starting and stopping. To do that, you could start the server as a background job before using phantomjs, and kill it afterwards using some insults with kill $! – which kills the last backgrounded job.

node app &# doesn't have to be a node app, though
sleep 5# give it some time to start listening# gather critical css in phantomjskill $! # kills `node app`

If you think sleep 5 is a lousy way of waiting for the app to begin listening, you could use the following loop to replace it – in unix systems. It checks whether an app is listening on the provided TCP $PORT every 100ms, and continues when it finds a listening process. If you wanted to do the same in Windows systems, check out the code for process-finder.

Inlining CSS Across Multiple Components

By critical content we’re talking about the content that’s immediately visible when your page loads. The content usually depends on which page you’re looking at, but the CSS and layout isn’t that different across pages if we’ve architected it properly, in such a way that it’s made out of small reusable components that make up the CSS for the entire site.

Nevertheless, we have to draw the line somewhere. Critical CSS rules seldom vary across different endpoints for the same route in a web app, – all of my articles share the vast majority of their CSS rules. For example, /articles/redesign and /articles/inlining-critical-css both have the navigation bar, the article’s title, an introduction, an ad, and then the article. However, compare that to my home page.

Critical content in the home page of Pony Foo

The home page is radically different, featuring a column layout and whatnot, and attempting to identify critical CSS for both of these pages at the same time would be kind of a moot point. If you went that road you’d eventually end up inlining the CSS for your entire site! In order to address that, I’ve found routes to be a good parameter. I’ve identified a few key areas in my site, and adjusted my script to build multiple critical CSS files, one for each key component in the site. The Bash function below gathers critical CSS from a given endpoint in my app and saves it into a file named by the component that endpoint represents.

Next, I picked a few components in my site: the landing page, the login page, any article, the history, search, and the 404 page. I gather critical CSS for each of those and gather places that in different files.

My server then decides what component it’s rendering, and inlines the CSS for that component. If the component doesn’t exist, then CSS blocks rendering. This is useful for those cases where you don’t want to go through the trouble of inlining CSS since it’s not worth it for you, like when a page is behind authentication and you think most users will have cached your CSS by then. The code below inlines critical CSS, defers the rest, and provides a fallback through a <noscript> tag.

If no critical CSS is available for our component, then we fall back to the blocking <link> tag.

You can measure its impact simply by implementing the solution and comparing with what you already had.

Please post comments in english.

Scott Galloway wroteon October 14, 2015

Very interesting, but this changes slightly with HTTP/2, with that it may be better (MAY, as usual test or be damned ;)) to appropriately set the prioritization for an external resource (with all the advantages around multiplexing, caching etc…)

Naturally. HTTP/2 changes a lot of established “best practices” into “mildly useful at best, a drawback at worst”, but we have to keep in mind that HTTP/2 won’t become generally available for a few years (esp. in older browsers playing catch-up). Until then, this technique will help us alongside spriting, concatenation, and whatnot.