Speeding Up Tailwind CSS Builds
Published: 2020-11-11 01:03:24

---
title: Speeding Up Tailwind CSS Builds
published: true
date: 2020-10-13 12:29:00 UTC
tags: 
canonical_url: https://nystudio107.com/blog/speeding-up-tailwind-css-builds
---

# Speeding Up Tailwind CSS Builds

### Learn how to opti­mize your Tail­wind CSS PostC­SS build times to make local devel­op­ment with Hot Mod­ule Replace­ment or Live Reload orders of mag­ni­tude faster!

Andrew Welch / [nystudio107](https://nystudio107.com)

![Speeding up tailwind css builds2](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x675_crop_center-center_82_line/10743/speeding-up-tailwind-css-builds2.jpg)

[Tail­wind CSS](https://tailwindcss.com/) is a util­i­ty-first CSS frame­work that we’ve been using for sev­er­al years, and it’s been pret­ty fan­tas­tic. We first talked about it on the [Tail­wind CSS util­i­ty-first CSS with Adam Wathan](https://devmode.fm/episodes/tailwind-css-utility-first-css-with-adam-watham) episode of [dev​Mode​.fm](https://devmode.fm/) way back in 2017!

We use it in the base of every project we do, as part of a web­pack build process described in the [An Anno­tat­ed web­pack 4 Con­fig for Fron­tend Web Devel­op­ment](https://dev.to/gaijinity/an-annotated-webpack-4-config-for-frontend-web-development-1p36) article.

One issue that we’ve run into recent­ly is that build times can be slow in local devel­op­ment, where you real­ly want the speed as you are build­ing out the site CSS.

This arti­cle will lead you through how to opti­mize your Tail­wind CSS build process so that your [hot mod­ule replace­ment](https://webpack.js.org/concepts/hot-module-replacement/) / live reload will be fast again, and explains what’s going on under the hood.

So let’s dive in!

## Fram­ing the Problem

The prob­lem I was hav­ing was that a sim­ple edit of one of my <kbd>.pcss</kbd> files would result in a good 10 sec­ond or more delay as the CSS was rebuilt.

Grant­ed, one of the love­ly things about Tail­wind CSS is that you don’t have to write much cus­tom CSS; most­ly you’re putting util­i­ty class­es in your markup.

<aside>
                                How­ev­er, when you do want to add cus­tom <span>CSS</span>, it should­n’t be this painful
                            </aside>

So if I want­ed to do some­thing like change the back­ground col­or of a par­tic­u­lar CSS class, instead of the instant feed­back I was used to from web­pack hot mod­ule replace­ment, I’d be wait­ing a good while for it to recompile.

I wrote up [Tail­wind CSS issue #2544](https://github.com/tailwindlabs/tailwindcss/issues/2544), and also cre­at­ed a min­i­mal GitHub repo [nys­tu­dio107/­tail­wind-css-per­for­mance](https://github.com/nystudio107/tailwind-css-performance) to repro­duce it.

But then start­ed spelunk­ing a bit fur­ther to see if I could find a way to mit­i­gate the situation.

I found a tech­nique that sped up my build times immea­sur­ably, and you can use it too.

## The Prob­lem Setup

My orig­i­nal base Tail­wind CSS set­up looked rough­ly like this:

```

css
├── app.pcss
├── components
│ ├── global.pcss
│ ├── typography.pcss
│ └── webfonts.pcss
├── pages
│ └── homepage.pcss
└── vendor.pcss

```

The meat of this is the app.css, which looked like this:

```

/**
 * app.css
 *
 * The entry point for the css.
 *
 */

/**
 * This injects Tailwind's base styles, which is a combination of
 * Normalize.css and some additional base styles.
 */
 @import "tailwindcss/base";

/**
 * This injects any component classes registered by plugins.
 *
 */
@import 'tailwindcss/components';

/**
 * Here we add custom component classes; stuff we want loaded
 * *before* the utilities so that the utilities can still
 * override them.
 *
 */
@import './components/global.pcss';
@import './components/typography.pcss';
@import './components/webfonts.pcss';

/**
 * This injects all of Tailwind's utility classes, generated based on your
 * config file.
 *
 */
@import 'tailwindcss/utilities';

/**
 * Include styles for individual pages
 *
 */
@import './pages/homepage.pcss';

/**
 * Include vendor css.
 *
 */
 @import 'vendor.pcss';

```

This <kbd>app.pcss</kbd> file then gets import­ed into my <kbd>app.ts</kbd> via:

```

// Import our CSS
import '../css/app.pcss';

```

This caus­es web­pack to be aware of it, so the <kbd>.pcss</kbd> gets pulled into the build pipeline, and gets processed.

And then my <kbd>postcss.config.js</kbd> file looked like this:

```

module.exports = {
    plugins: [
        require('postcss-import')({
            plugins: [
            ],
            path: ['./node_modules'],
        }),
        require('tailwindcss')('./tailwind.config.js'),
        require('postcss-preset-env')({
            autoprefixer: { },
            features: {
                'nesting-rules': true
            }
        })
    ]
};

```

And we have a super-stan­dard <kbd>tailwind.config.js</kbd> file:

```

module.exports = {
  theme: {
    // Extend the default Tailwind config here
    extend: {
    },
    // Replace the default Tailwind config here
  },
  corePlugins: {},
  plugins: [],
};

```

## The Prob­lem

This set­up above is pret­ty much what is laid out Tail­wind CSS docs sec­tion on [Using with Pre­proces­sors: PostC­SS as your pre­proces­sor](https://tailwindcss.com/docs/using-with-preprocessors#using-post-css-as-your-preprocessor).

Tail­wind CSS is built using [PostC­SS](https://postcss.org/), so it makes sense that if you’re using a build­chain that uses [web­pack](https://webpack.js.org/) or [Lar­avel Mix](https://laravel.com/docs/8.x/mix) (which uses web­pack under the hood) or [Gulp](https://gulpjs.com/) that you’d lever­age PostC­SS there, too.

The doc­u­men­ta­tion rec­om­mends that if you’re using the [postc­ss-import](https://github.com/postcss/postcss-import) plu­g­in (which we are, and is a very com­mon­ly used PostC­SS plu­g­in), that you change this:

```

@tailwind base;
@import "./custom-base-styles.css";

@tailwind components;
@import "./custom-components.css";

@tailwind utilities;
@import "./custom-utilities.css";

```

To this:

```

@import "tailwindcss/base";
@import "./custom-base-styles.css";

@import "tailwindcss/components";
@import "./custom-components.css";

@import "tailwindcss/utilities";
@import "./custom-utilities.css";

```

This is because <kbd>postcss-import</kbd> strict­ly adheres to the CSS spec and dis­al­lows <kbd>@import</kbd> state­ments any­where except at the very top of a file, so we can’t mix them in togeth­er with our oth­er CSS or <kbd>@tailwind</kbd> directives.

<aside>
                                And this is where things start to go pear-shaped
                            </aside>

When we use <kbd>@import "tailwindcss/utilities";</kbd> instead of <kbd>@tailwind utilities;</kbd> all that’s real­ly hap­pen­ing is the <kbd>tailwindcss/utilities.css</kbd> file is imported:

```

@tailwind utilities;

```

So we’re just side-step­ping the <kbd>@import</kbd> loca­tion require­ment in <kbd>postcss-import</kbd> by adding a lay­er of indirection.

But we can have a look at <kbd>node_modules/tailwindcss/dist/</kbd> to get a rough idea how large this gen­er­at­ed file is going to be:

```

❯ ls -alh dist
total 43568
drwxr-xr-x 12 andrew staff 384B Sep 19 11:34 .
drwxr-xr-x 19 andrew staff 608B Sep 19 11:35 ..
-rw-r--r-- 1 andrew staff 11K Oct 26 1985 base.css
-rw-r--r-- 1 andrew staff 3.1K Oct 26 1985 base.min.css
-rw-r--r-- 1 andrew staff 1.9K Oct 26 1985 components.css
-rw-r--r-- 1 andrew staff 1.3K Oct 26 1985 components.min.css
-rw-r--r-- 1 andrew staff 5.4M Oct 26 1985 tailwind-experimental.css
-rw-r--r-- 1 andrew staff 4.3M Oct 26 1985 tailwind-experimental.min.css
-rw-r--r-- 1 andrew staff 2.3M Oct 26 1985 tailwind.css
-rw-r--r-- 1 andrew staff 1.9M Oct 26 1985 tailwind.min.css
-rw-r--r-- 1 andrew staff 2.2M Oct 26 1985 utilities.css
-rw-r--r-- 1 andrew staff 1.8M Oct 26 1985 utilities.min.css

```

(inci­den­tal­ly, if you look close­ly, you’ll also have learned Adam Wathan’s birthday)

We can see that the <kbd>utilities.css</kbd> file weighs in at a hefty <kbd>2.2M</kbd> itself; and while this comes out in the wash for pro­duc­tion builds when you’re using [PurgeC­SS](https://purgecss.com/) as rec­om­mend­ed in [Tail­wind CSS docs: Con­trol­ling file size](https://tailwindcss.com/docs/controlling-file-size), it can be prob­lem­at­ic for local development.

So but why is this a prob­lem? If we’re just <kbd>@import</kbd>​’ing the file, why would this be so slow?

The rea­son twofold:

1. Although Tail­wind CSS has [opti­miza­tions in place](https://github.com/tailwindlabs/tailwindcss/compare/v1.7.1..v1.7.2#diff-cf591df84e9e87cb0fa15f602337bfc34479a6a5eedb03fffce0cdf0e756bd80R298)to mit­i­gate it, there’s still a ton of CSS gen­er­a­tion that has to hap­pen each time a change is made in any of your <kbd>.pcss</kbd> files. We lumped them all in togeth­er, so they all get rebuild together.
2. The <kbd>postcss-import</kbd> plu­g­in [actu­al­ly pars­es](https://github.com/postcss/postcss-import/blob/master/index.js#L129) any files you <kbd>@import</kbd>, look­ing for oth­er <kbd>@import</kbd> state­ments in that import­ed file, and it does this on the CSS that the <kbd>@tailwind</kbd> direc­tive gen­er­ates, too.

And our result­ing utilities.css file has by default over 100,000 lines of CSS gen­er­at­ed & parsed through:

```

❯ wc -l utilities.css
  102503 utilities.css

```

So that’s not good.

## The Solu­tion

So what can we do? It’s inher­ent to Tail­wind CSS that it’s going to cre­ate a ton of util­i­ty CSS for you, and that gen­er­a­tion can only be opti­mized so much.

I start­ed think­ing of var­i­ous caching mech­a­nism that could be added to Tail­wind CSS, but I real­ized the right solu­tion was to just lever­age the platform.

I remem­bered an old Comp Sci maxim:

<aside>
                                The fastest code is the code you nev­er have to execute
                            </aside>

We’re already using web­pack, which adroit­ly han­dles tons of imports of var­i­ous kinds through a vari­ety of load­ers… why not just break our <kbd>.pcss</kbd> up into chunks, and let web­pack sort it out?

## The Solu­tion Setup

So that’s exact­ly what I did in the [solu­tion branch of nys­tu­dio107/­tail­wind-css-per­for­mance](https://github.com/nystudio107/tailwind-css-performance/tree/solution).

Now our CSS direc­to­ry looks like this:

```

css
├── app-base.pcss
├── app-components.pcss
├── app-utilities.pcss
├── components
│ ├── global.pcss
│ ├── typography.pcss
│ └── webfonts.pcss
├── pages
│ └── homepage.pcss
├── tailwind-base.pcss
├── tailwind-components.pcss
├── tailwind-utilities.pcss
└── vendor.pcss

```

Our <kbd>app.pcss</kbd> file has been chopped up into 6 sep­a­rate .pcss files that cor­re­spond with Tail­wind’s <kbd>base</kbd>, <kbd>components</kbd>, and <kbd>utilities</kbd> methodology:

```

/**
 * This injects Tailwind's base styles, which is a combination of
 * Normalize.css and some additional base styles.
 */
@tailwind base;



/**
 * Here we add custom base styles, applied after the tailwind-base
 * classes
 *
 */



/**
 * This injects any component classes registered by plugins.
 *
 */
@tailwind components;



/**
 * Here we add custom component classes; stuff we want loaded
 * *before* the utilities so that the utilities can still
 * override them.
 *
 */
@import './components/global.pcss';
@import './components/typography.pcss';
@import './components/webfonts.pcss';



/**
 * This injects all of Tailwind's utility classes, generated based on your
 * config file.
 *
 */
@tailwind utilities;



/**
 * Include styles for individual pages
 *
 */
@import './pages/homepage.pcss';

/**
 * Include vendor css.
 *
 */
 @import 'vendor.pcss';

```

And then these <kbd>.pcss</kbd> files then get import­ed into our <kbd>app.ts</kbd> via:

```

// Import our CSS
import '../css/tailwind-base.pcss';
import '../css/app-base.pcss';
import '../css/tailwind-components.pcss';
import '../css/app-components.pcss';
import '../css/tailwind-utilities.pcss';
import '../css/app-utilities.pcss';

```

Noth­ing else was changed in our con­fig or setup.

This allows web­pack to han­dle each chunk of import­ed <kbd>.pcss</kbd> sep­a­rate­ly, so the Tail­wind-gen­er­at­ed CSS (and impor­tant­ly the huge <kbd>utilities.css</kbd>) only needs to be rebuilt if some­thing that affects it (like the <kbd>tailwind.config.js</kbd>) is changed.

<aside>
                                Lever­age the platform
                            </aside>

Changes to any of the <kbd>.pcss</kbd> files that we write are rebuilt sep­a­rate­ly & instantly.

💥

## Bench­mark­ing Prob­lem vs. Solution

I did a few infor­mal bench­marks while test­ing all of this out. I used a 2019 Mac­Book Pro with 64gb RAM.

For the test, all I did was change the <kbd>background-color: yellow;</kbd> to <kbd>background-color: blue;</kbd> in the <kbd>css/components/global.pcss</kbd> file.

When we do a rebuild using the ​“Prob­lem Set­up”, the <kbd>webpack-dev-server [WDS]</kbd> out­put in the browser’s Devel­op­er JavaScript Con­sole looks like this:

```

[WDS] App updated. Recompiling...
[WDS] App hot update...
[HMR] Checking for updates on the server...
[HMR] Updated modules:
[HMR] - ../src/css/app.pcss
[HMR] App is up to date.

```

…and it took <kbd>11.74s</kbd> to do this recom­pile on my 2019 Mac­Book Pro.

Notice that it rebuild the entire <kbd>app.pcss</kbd> here.

When we do a rebuild using the ​“Solu­tion Set­up”, the <kbd>webpack-dev-server [WDS]</kbd> out­put in the browser’s Devel­op­er JavaScript Con­sole looks like this:

```

[WDS] App updated. Recompiling...
[WDS] App hot update...
[HMR] Checking for updates on the server...
[HMR] Updated modules:
[HMR] - ../src/css/app-components.pcss
[HMR] App is up to date.

```

…and it only took <kbd>0.52s</kbd> to do this HMR rebuild.

Notice that it rebuilt _only_ the <kbd>app-components.pcss</kbd> here, and deliv­ered just the diff of it to the brows­er in a high­ly effi­cient manner.

<aside>
                                So with our changes, it’s now <span>22</span>.<span>5</span>x faster
                            </aside>

This is a nice gain, and makes the devel­op­ment expe­ri­ence much more enjoyable!

Hap­py sail­ing! **≈ 🚀**

## Further Reading

_If you want to be notified about new articles, follow [nystudio107](https://twitter.com/nystudio107) on Twitter._

<small>Copyright ©2020 nystudio107. Designed by nystudio107</small>