Inlining critical CSS
During a recent (highly recommended) talk by Addy Osmani as part of Chrome's web.dev/live event, Addy explains how Chloé optimised their website for performance and Google's core web vitals. There are a lot of great takeaways from the talk, some of which could be implemented into your own project relatively easily; however one technique stuck out slightly for me.
To dynamically generate the critical CSS we developed a script that runs at build-time extracting all the CSS blocks containing the custom
critical: thisproperty and inlining them in the head of the page. The inlined CSS rules are removed from the original CSS files, which are loaded with low priority using the media=’print’ technique.
The above is taken from the case study from Chloé’s engineering blog.
The goal of inlining critical CSS is to prevent a flash of unstyled content (FOUC). CSS is a render-blocking resource, meaning that it needs to be downloaded and parsed to create the CSSOM. This is then combined with the DOM to create the render tree, which is used to layout the different elements and feed the paint process which ultimately outputs the pixels to the screen. Having a single large CSS file will delay the start render, as all CSS, regardless of whether it will be used or not will have to be downloaded. Inlining the CSS, will also avoid a network request to download the CSS file.
So can I inline all my CSS?Nope. You should try to keep your initial HTML + CSS under 14KB. Why?
Unfortunately, stylesheets do not support the
async attribute, as
<script> do and a variety of different approaches to asynchronously downloading the stylesheets have been implemented. The simplest and widely supported approach is the
media="print" technique quoted earlier. It might seem ugly, but it works well.
Therefore, given the following SCSS file: We would expect it to output critical CSS and a
media="print" link to the remaining stylesheet.
What's this magic?
The Chloé engineering team developed a script that extract the CSS blocks marked with
critical: this. I have seen similar scripts which worked by extracting blocks prefixed by
// !critical or extracting CSS which is generated on the server-side as critical (in the case of universal applications). There are also npm modules which generate the critical CSS using puppeteer, such as penthouse.
But in most cases, I prefer something simpler and I am able to achieve the similarly good results through a simple webpackconfiguration using chunking and HtmlWebpackPlugin. For starters, I separate critical and non-critical CSS into two SCSS files. From experience, a module would either be critical or not (for example a hero banner may be treated as critical) and all its SCSS should belong in one file anyway. Webpack is then configured to extract all SCSS from files named
critical.scss into a separate chunk.
The resultant CSS file (using MiniCssExtractPlugin) then needs to be injected into our HTML using HtmlWebpackPlugin. This requires that HtmlWebpackPlugin is configured with the option
inject: false so that we can create our own HTML output. We then add a condition to output the contents of the chunk and thus inline our critical CSS.
With relatively little effort, we are able to inline our critical CSS. This technique works both with CSS modules and not and can be easily added to an existent project.
Performance-First React Template
While writing this blog, I decided to clean up my Webpack configurations and build scripts to make them more accessible. This work is available in Performance-first React Template and includes the critical CSS configuration described in this blog and much more if you would be interested in taking a look. Feedback is highly welcome, so feel free to reach out to me on Twitter.
Thank you for reading.