Using the latest Tailwind CSS (1.8.13 as of this writing), I'm seeing slowness very similar to https://github.com/tailwindlabs/tailwindcss/issues/1620 & also https://github.com/tailwindlabs/tailwindcss/issues/443, the performance of using HMR with webpack-dev-server and webpack 4 or 5 is quite slow.
It takes about 10 seconds on my MacBook Pro 2019 just changing a single color in a .pcss file, and it appears externally that it's rebuilding everything each time. The building of Tailwind CSS seems to have gotten slower and slower as the amount of utilities it includes have gone up.
I'm not sure what the caching implemented in 1.7.2 does, but in a long running process (and maybe it's already doing this but) what if all of the Tailwind-specific imports like:
@import "tailwindcss/base";
...were cached in a long running process, so it just returns the pre-generated blob? I'd imagine you're probably already doing this, but have any instrumentation or profiling been hooked up to the build to determine where the bottlenecks are?
My postcss.config.js looks 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 the whole setup is essentially what's here: https://nystudio107.com/blog/an-annotated-webpack-4-config-for-frontend-web-development#tailwind-css-post-css-config
It's not doing anything fancy re: the PostCSS part of the build, but its extremely slow compared to the HRM of JavaScript modules, etc.
I tried removing postcss-preset-env to see if it made a difference, but it doesn't seem to.
Can you create a GitHub repo that we can use to test and benchmark? Setting it up myself means I'm just gonna put this off for months and months unfortunately, hah.
Here ya go, all packaged up in a Docker container:
I did a little more research on this tonight, and removing Tailwind CSS entirely from the project resulted in instantaneous HMR'd CSS.
Narrowing it down further, my .pcss has the following lines:
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
Removing @import 'tailwindcss/utilities'; results in the compilation being extremely fast; which I suppose makes sense, because it is generating the most CSS.
If we're just doing a raw import of a pre-generated file here, I'm guessing that it's just due to the massive side of the CSS file, and there may not be room for optimizations here.
Going on this, and some other various issues filed here, I refactored it to split off the .pcss into 4 separate files which I import individually in my app.ts:
import '../css/tailwind-before.pcss';
import '../css/app-before.pcss';
import '../css/tailwind-after.pcss';
import '../css/app-after.pcss';
tailwind-before.pcss has ->
/**
* This injects Tailwind's base styles, which is a combination of
* Normalize.css and some additional base styles.
*/
@tailwind base;
/**
* This injects any component classes registered by plugins.
*
*/
@tailwind components;
app-before.pcss has ->
/**
* 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';
tailwind-after.pcss has ->
/**
* This injects all of Tailwind's utility classes, generated based on your
* config file.
*
*/
@tailwind utilities;
app-after.pcss has ->
/**
* Include styles for individual pages
*
*/
@import './pages/homepage.pcss';
/**
* Include vendor css.
*
*/
@import 'vendor.pcss';
So all we've done here is taken one large .pcss bundle and chopped it up into 4 separate ones that can be individually recompiled, and thus also individually HMR'd.
This results in near instantaneous rebuilds for any of my .pcss that I may be writing, while also allowing me to use Tailwind CSS utilities, etc. that are generated dynamically as a result of config file changes..
All of the CSS is still imported in the right order, and they all also build into one combined CSS file for a production build.
I'll likely write this up for others who may run into it.
Wrote the article up in:
@adamwathan this could be nice to be added in the documentation
Also props Andrew! This will help tons of people.
I am surprised splitting up the files makes a difference — to me that points to the build process not doing what it should because it means each file is being processed independently which can have surprising side effects in your CSS. The CSS should be fully concatenated into a single file before being processed by PostCSS for everything to work properly. Otherwise you can't use @apply across files for example.
If someone can put together a super minimal simple example that demonstrates the issue that would be helpful, the provided project with the Docker container looks super complicated.
Tailwind builds everything from scratch in < 4s for me so I wonder if something in the configuration is causing Tailwind to run multiple times.

@adamwathan did you see the part about the postcss-import plugin? It looks to me like it might end up parsing each statement in the imported CSS files, looking for other @import statements.
I haven't tried taking postcss-import out of the mix to confirm its impact on this (if any) but it certainly could be a factor.
I realize that we can't @apply across multiple files, but since I'm lumping all component CSS together, I assumed it'd be fine (that's where my @applys would likely be coming from). I've noted this in the article. I haven't run into any other side-effects, but I'll definitely note them if I do.
As for the bare minimum repo, I'll have to leave that for someone else, or me when I have more time to devote to this.
Ahh you are doing JS imports for each file rather than PostCSS imports, that makes sense that it would speed things up in that way but comes with the consequence I mentioned of your files being processed in isolation from each other.
mmm yeah I removed the postcss-import plugin entirely from postcss.config.js:
module.exports = {
plugins: [
require('tailwindcss')('./tailwind.config.js'),
]
};
...and reduced by app.pcss to just:
body {
background-color: blue;
}
@tailwind base;
@tailwind components;
@tailwind utilities;
...and the rebuild times are still as slow as mentioned previously, so it's either Tailwind-related or build system-related (or more likely a mix of both).
Can only confirm that we're having similar issues. Our build itself is pretty quick but HMR with css being imported through js is very slow. So much it sometimes crashes Chrome 😅
If the floor here is 3.6s on a modern MacBook Pro, raw execution, no tooling, no build system, no VM/Docker, etc. in real-world situations it's only going to get worse.
When using HMR, especially iteratively, this can slow down the developer experience significantly.
I don't know enough about Tailwind's internals, but perhaps something like a static cache that just returns the utilities rather than regenerating them if the config hasn’t changed would be ideal.
Or perhaps other shortcuts/caching layers could be implemented if process.node_env is development, similar to how WDS does in-memory compilation and takes other shortcuts in development.
Other moonshots: rewrite the core in Rust or the like and bundle it up via WASM. Yeah, I know, it'd need PostCSS and a whole bunch of other things to be also similarly ported over... just seeing the gains from esbuild, and figured I'd put it out there.
Yep something we can continue to work on when we’re able to prioritize it. In the mean time I’m 100% open to pull requests that improve performance if you have any ideas you’d like to try 👍🏻
I hear ya @adamwathan -- I think to do this effectively, the person would need to know Tailwind's internals really well, which unfortunately I'm not able to delve into currently. So I'll use my hack for now, until it can get addressed in core. Thx!
For what it's worth I definitely see much faster rebuilds during a watch process than on initial build at least. This is from a simple postcss-cli set up where I'm changing my CSS file to trigger the rebuilds:

Under 1s which given how infrequently you typically update CSS with Tailwind feels adequate to me. Would be interesting to see some flame charts of the webpack setup where CSS is imported directly into JS to see if the slowness is within Tailwind or perhaps the result of webpack doing something expensive with such a large bundle of CSS. Would be nice to speed it up either way but we will have less power to do so if it's just that "importing a big CSS file via webpack is slow".
Man, I wish I was seeing build times anything close to that. Good idea on swapping in a generic import, I'm going to try that now. Will report back.
I ended up using a somewhat middleground solution. Using @khalwat 's seperation into seperate tailwindcss files that I import in my javascript entry point, but stil using the @import 'tailwindcss/base'; syntax in those files. Gives me the adventage of still having all my @apply rules available and the only time the hot reload is slow, is when I change my actual tailwind file.
Got my HMR time to this with the setup:
+194 hidden modules
「wdm」: Compiled successfully.
「wdm」: Compiling...
[wdm」: Hash: a469c61182996c46a2d3
Version: webpack 4.39.3
Time: 327ms
Built at: 10/14/2020 2:58:03 PM
Updating a property in the tailwind config file gives me this:
Version: webpack 4.39.3
Time: 7838ms
Built at: 10/14/2020 3:01:46 PM
Since we're not updating the tailwind config that much, this seems like a workable solution
So @adamwathan it turns out you're 100% right on this, I did some timings, and there was effectively no difference between:
@import 'tailwindcss/utilities'utilities.css via @import 'tailwindcss/dist/utilities.cssutilities.css into the app.pcss file (just to see if the postcss-import plugin added overheadTL;DR: we can cut our build process time in half by not generating CSS sourcemaps, and by setting devtool: 'eval-cheap-module-source-map' in the webpack.dev.js config. Tailwind CSS's build times still could be reduced, but the tooling _around_ the massively generated CSS files is what's causing a good bit of the slowdown.
The separated builds described in the article I still think are useful, because it get us down to < 0.5s for the HMR reload times, but the 3.2s HMR times I'm seeing now are at least usable. I need to update the article with these build settings too.
The details:
@import'd tailwindcss/utilities:webpack_1 | Version: webpack 4.44.2
webpack_1 | Time: 7260ms
webpack_1 | Built at: 10/14/2020 1:13:05 PM
webpack_1 | Asset Size Chunks Chunk Names
webpack_1 | app.eaa21190e6318cfca3af.hot-update.js 8.77 MiB app [emitted] [immutable] [hmr] app
webpack_1 | eaa21190e6318cfca3af.hot-update.json 45 bytes [emitted] [immutable] [hmr]
webpack_1 | js/app.js 10.8 MiB app [emitted] app
webpack_1 | manifest.json 179 bytes [emitted]
webpack_1 | + 2 hidden assets
.....
@import'd tailwindcss/dist/utilities.css:webpack_1 | Version: webpack 4.44.2
webpack_1 | Time: 7524ms
webpack_1 | Built at: 10/14/2020 1:11:11 PM
webpack_1 | Asset Size Chunks Chunk Names
webpack_1 | 56687676ce73de1152f4.hot-update.json 45 bytes [emitted] [immutable] [hmr]
webpack_1 | app.56687676ce73de1152f4.hot-update.js 14.5 MiB app [emitted] [immutable] [hmr] app
webpack_1 | js/app.js 16.5 MiB app [emitted] app
webpack_1 | manifest.json 179 bytes [emitted]
webpack_1 | + 2 hidden assets
.....
utilities.csswebpack_1 | Version: webpack 4.44.2
webpack_1 | Time: 7102ms
webpack_1 | Built at: 10/14/2020 1:09:51 PM
webpack_1 | Asset Size Chunks Chunk Names
webpack_1 | app.fa2c53f7c35b6db7db11.hot-update.js 14.2 MiB app [emitted] [immutable] [hmr] app
webpack_1 | fa2c53f7c35b6db7db11.hot-update.json 45 bytes [emitted] [immutable] [hmr]
webpack_1 | js/app.js 16.2 MiB app [emitted] app
webpack_1 | manifest.json 179 bytes [emitted]
webpack_1 | + 2 hidden assets
.....
So then I looked and noticed how positively massive the generated hot updates were... 8.77M for #1 @import 'tailwindcss/utilities' and a whopping 14.5M for #2 @import 'tailwindcss/dist/utilities.css
Turns out almost all of this is the sourcemaps, which I had set to inline-source-map for devtool in my webpack config but also, in my configurePostCssLoader I had sourceMap: true for both the postcss-loader and the css-loader.
This is why the #2 @import 'tailwindcss/dist/utilities.css hot update was so huge, it looks like it was generated sourcemaps for both loaders.
Here are some timing tests:
sourceMap: false for css-loader & postcss-loader and devtool: 'inline-source-map'
webpack_1 | Version: webpack 4.44.2
webpack_1 | Time: 4103ms
webpack_1 | Built at: 10/14/2020 1:30:14 PM
webpack_1 | Asset Size Chunks Chunk Names
webpack_1 | 420f72e6cc40facb1510.hot-update.json 45 bytes [emitted] [immutable] [hmr]
webpack_1 | app.420f72e6cc40facb1510.hot-update.js 6.53 MiB app [emitted] [immutable] [hmr] app
webpack_1 | js/app.js 8.53 MiB app [emitted] app
webpack_1 | manifest.json 179 bytes [emitted]
webpack_1 | + 2 hidden assets
....
sourceMap: false for css-loader & postcss-loader and devtool: 'eval-cheap-module-source-map'
webpack_1 | Version: webpack 4.44.2
webpack_1 | Time: 3296ms
webpack_1 | Built at: 10/14/2020 1:59:52 PM
webpack_1 | Asset Size Chunks Chunk Names
webpack_1 | 7059df1a7293e89c81d1.hot-update.json 45 bytes [emitted] [immutable] [hmr]
webpack_1 | app.7059df1a7293e89c81d1.hot-update.js 5.99 MiB app [emitted] [immutable] [hmr] app
webpack_1 | js/app.js 7.98 MiB app [emitted] app
webpack_1 | manifest.json 179 bytes [emitted]
webpack_1 | + 2 hidden assets
build time is really slow even with hugo , which under the hood uses esbuild as its compiler which is the fastest compiler...
I have one project and building that from cold start is 8secs... compared to non tailwind project around 200ms...
As much as i Love tailwind,tailwind is So SLOW, and This Literally Increases Build time Specially for Cloud Providers like netlify.
Hope tailwind gets bump to re architecture it, avoiding this slow building process...
Me i Dont Use @apply at all, Its always much better to write, your own css ... If that is the reason building takes too many round trip to complete maybe you can have an option to drop a way to use that feature... thus improving speed...
tailwind already has good utility classes and its stupid to use @apply , if you need custom classes just write it on plain css... or inline it with html
Going to close this now because the underlying issue is that webpack performs poorly with large CSS files and there's just nothing we can do about that. Making Tailwind smaller by default is not really an option as it would severely cripple the usefulness of the framework. This would be better as an issue on the webpack repo around how to improve performance around the handling of large CSS files. Not trying to pass the buck but literally nothing we can do about it other than dedicate resources to making improvements directly to webpack which is not out of the question, but still work that would happen in their project and not in ours.
Yep, agreed re: closing the issue.
The only thing I'd add is that I think it's a common problem, so if generating less CSS is out of the question, perhaps a direction Tailwind could take is some kind of official way to handle the type of "CSS-splitting" mentioned in the article.
I realize Tailwind is intended to work as one glob that's parsed by PostCSS, but perhaps some kind of official documentation on the problems people can run into re: performance, and the ways around it.
I got the full Tailwind CSS build -- no CSS splitting, so global @apply -- down to 1s or so on my MacBook Pro:
webpack_1 | example-project (webpack 5.1.3) compiled successfully in 1070 ms
@jan-dh I'm not sure how adding @import 'tailwindcss/base' makes a difference here? The Tailwind base.css just has some simple CSS reset rules and the like.
I've been able to port a number or projects to use the technique described in my article without ill effect, and with @apply seeming to work fine.
I'm sure there's something you miss out on but splitting it this way, but it seems to be working as expected for me.
¯_(ツ)_/¯
Most helpful comment
I did a little more research on this tonight, and removing Tailwind CSS entirely from the project resulted in instantaneous HMR'd CSS.
Narrowing it down further, my
.pcsshas the following lines:Removing
@import 'tailwindcss/utilities';results in the compilation being extremely fast; which I suppose makes sense, because it is generating the most CSS.If we're just doing a raw import of a pre-generated file here, I'm guessing that it's just due to the massive side of the CSS file, and there may not be room for optimizations here.
Going on this, and some other various issues filed here, I refactored it to split off the
.pcssinto 4 separate files which I import individually in myapp.ts:tailwind-before.pcsshas ->app-before.pcsshas ->tailwind-after.pcsshas ->app-after.pcsshas ->So all we've done here is taken one large
.pcssbundle and chopped it up into 4 separate ones that can be individually recompiled, and thus also individually HMR'd.This results in near instantaneous rebuilds for any of my
.pcssthat I may be writing, while also allowing me to use Tailwind CSS utilities, etc. that are generated dynamically as a result of config file changes..All of the CSS is still imported in the right order, and they all also build into one combined CSS file for a production build.
I'll likely write this up for others who may run into it.