Critical CSS and Webpack: Automatically Minimize Render-Blocking CSS
When a web page is accessed, Google wants it to only load what’s useful for the initial view, and use idle time to load anything else. That way, the user can see the page as early as possible.
But what about CSS? For this, we can minimize render-blocking by isolating the CSS needed for above-the-fold content (a.k.a. the critical CSS) and loading that first. We can then load the non-critical CSS afterwards.
Isolating critical CSS is something that can be done programmatically, and in this article I’ll show you how to delegate it to your Webpack pipeline.
Note: this article was originally posted here on the Vue.js Developers blog on 2017/07/24
What does render-blocking mean?
If a resource is “render-blocking”, it means the browser can’t display the page until the resource is downloaded or otherwise dealt with.
Typically, we will load our CSS in a render-blocking way by linking to our stylesheet in the
head of the document, like this:
When this page is loaded by a web browser, it will read it from top to bottom. When the browser gets to the
link tag, it will start downloading the stylesheet straight away, and will not render the page until it’s finished.
For a large site, particularly one with a generously-sized framework like Bootstrap, the stylesheet might be several hundred kilobytes, and the user will have to patiently wait until this fully downloads.
So, should we just link to the stylesheet in the
body, where rendering is not blocked? You could, but the thing is render-blocking is not entirely bad, and we actually want to exploit it. If the page rendered without any of our CSS loaded, we’d get the ugly “flash of unstyled content”:
The sweet-spot we want is where we render-block the page with the critical CSS that’s required to style the main view, but all non-critical CSS is loaded after the initial render.
Have a look at this simple page that I’ve built with Bootstrap and Webpack. This is what it looks like after it’s first rendering:
The page also has a modal which is opened by the “Sign up today” button. When opened, it looks like this:
For the first rendering of the page we’ll need CSS rules for the nav bar, the jumbotron, the button and a few other general rules for layout and fonts. But we won’t need the rules for the modal, since it won’t be shown immediately. With that in mind, here’s how we might isolate the critical CSS from the non-critical CSS:
If you’re on board with this concept, there are two questions that you might now find of interest:
- How can we discern our critical and non-critical CSS programmatically?
- How can we get our page to load the critical CSS before the first render and load the non-critical CSS after the first render?
I’ll briefly introduce you to the basic setup of this project, so when we reach the solution it’ll be quick to digest.
Firstly, I’m loading Bootstrap SASS into my entry file.
I’m using sass-loader to handle this, and I’m using it in conjunction with the Extract Text Plugin so that the compiled CSS goes into its own file.
I’m also using the HTML Webpack Plugin to create an HTML file in the build. It’s necessary for the solution, as you’ll soon see.
After I run a build, here’s what the HTML file looks like. Note that CSS is being loaded in the
head and will therefore block rendering.
Programmatically identifying critical CSS
Manually identifying the critical CSS would be a pain to maintain. To do it programmatically, we can use Addy Osmani’s aptly named Critical. This is a Node.js module that will read in an HTML document, and identify the critical CSS. It does a bit more than that as well, as we’ll see shortly.
The way that Critical identifies the critical CSS is by loading the page with PhantomJS, with a screen dimension you specify, and by extracting any CSS rules used in the rendered page.
Here’s how we can set it up for this project:
When executed, this will update the HTML file in the Webpack bundle output to:
It will also output a new CSS file e.g. style.96106fab.css (a hash is automatically added to the file name). This CSS file is the same as the original stylesheet, only with critical CSS stripped out.
Inlining critical CSS
You’ll notice that the critical CSS has been inlined into the
head of the document. This is optimal as the page doesn’t have to load it from the server.
Preload non-critical CSS
You’ll also notice that the non-critical CSS is loaded with a sophisticated-looking
preload value tells the browser to start fetching the non-critical CSS for pending use. But crucially,
preload is not render-blocking, so the browser will go ahead and paint the page whether the preload resource is completed or not.
onload attribute in the
link allows us to run a script when the non-critical CSS has eventually loaded. The Critical module automatically inlines a script into the document that provides a cross-browser compatible way of loading the non-critical stylesheet into the page.
Putting Critical into a Webpack pipeline
I’ve made a Webpack plugin called HTML Critical Webpack Plugin that is merely a wrapper for the Critical module. It will run after your files have been emitted from the HTML Webpack Plugin.
Here’s how you can include it in a Webpack project:
Note: you should probably only use this in a production build, not development, as it will make your build really slow!
Now that I’ve isolated critical CSS, and I’m loading the non-critical CSS in idle time, what do I get in the way of performance improvements?
I used the Chrome Lighthouse extension to find out. Keep in mind the metric we’re trying to optimise is Time To First Meaningful Paint, which basically tells us how long it is until the user can see something.
Before implementing critical CSS:
After implementing critical CSS:
As you can see, my app got a meaningful paint a full second earlier, and is interactive half a second earlier. In practice, you may not get such a dramatic improvement in your app, since my CSS was thoroughly bloated (I included the entire Bootstrap library), and in such a simple app I didn’t have many critical CSS rules.
Become a senior Vue developer in 2020.
Learn and master what professionals know about building, testing, and deploying, full-stack Vue apps in our latest course.