Inlining Critical CSS
Tags: webperf, webdev, webpack
Published: 2020-07-29 10:12:11

---
canonical_url: https://imkev.dev/inlining-critical-css
---
Cross-posted from: https://imkev.dev/inlining-critical-css

During a recent (highly recommended) talk by Addy Osmani as part of Chrome's [web.dev/live](https://web.dev/live) event, Addy explains how Chloé optimised their website for performance and Google's core [web vitals](https://web.dev/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: this` property 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](https://medium.com/ynap-tech/optimizing-cls-and-lcp-on-chloe-com-8280a036122a) 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?](https://hpbn.co/building-blocks-of-tcp/#slow-start)

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"`](https://www.filamentgroup.com/lab/load-css-simpler/) technique quoted earlier. It might seem ugly, but it works well.

Therefore, given the following SCSS file:
```
/* this bit is critical */
.list {
  width: 100%;
  padding: 0;
  margin: 0;
}

/* this bit is not critical as it only shows after user interaction or is below the fold */
.list-item {
  position: relative;
  display: block;
  padding: 1em;
  border-bottom: 1px solid rgba(0, 0, 0, 0.05);

  &:hover .remove {
    opacity: 1;
  }
}
```
We would expect it to output critical CSS and a `media="print"` link to the remaining stylesheet.
```
<head>
  ...
  <style>.list {width: 100%; padding: 0; margin: 0;}</style>
  <link rel="stylesheet" href="/style.css" media="print" onload="this.media='all'">
</head>
```

## 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](https://github.com/GoogleChrome/puppeteer), such as [penthouse](https://github.com/pocketjoso/penthouse).

But in most cases, I prefer something simpler and I am able to achieve the similarly good results through a simple [webpack](https://webpack.js.org/)configuration using chunking and [HtmlWebpackPlugin](https://github.com/jantimon/html-webpack-plugin). 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.
```
  optimization: {
    // ...
    splitChunks: {
      // ...
      cacheGroups: {
        criticalStyles: {
          name: "critical",
          test: /critical\.(sa|sc|c)ss$/,
          chunks: "initial",
          enforce: true,
        },
        // ...
      },
    },
    // ...
  }
```

The resultant CSS file (using [MiniCssExtractPlugin](https://github.com/webpack-contrib/mini-css-extract-plugin)) 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.
```
<% for (let index in htmlWebpackPlugin.files.css) { %>
  <% if (/critical(\..*)?\.css$/.test(htmlWebpackPlugin.files.css[index])) { %>
    <style>
        <%= compilation.assets[htmlWebpackPlugin.files.css[index].substr(htmlWebpackPlugin.files.publicPath.length)].source() %>
    </style>
  <% } else { %>
    <link rel="stylesheet" href=<%= `${process.env.CDN_URL}${htmlWebpackPlugin.files.css[index]}` %> media="print" onload="this.media='all'">
  <% } %>
<% } %>
```

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](https://github.com/kevinfarrugia/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 [@imkevdev](https://twitter.com/imkevdev/).

Thank you for reading.