I'm a developer & content creator, based in Ghent

Turbo monorepo with Tailwind V4 and Vite

I recently introduced Tailwind V4 into my Turbo monorepo using the @tailwindcss/vite plugin - and while this worked, HMR broke the reloading of the CSS.

When dealing with monorepo's, things tend to get a bit tricky, but Tailwind has you covered. In your tailwind.css file, you can simply set the base path as follows:

/* source('../') points to the root of the project */
@import "tailwindcss" source('../');

This allows you to run your commands from the project root, even though your actual app lives in a subdirectory (eg. apps/web) as is common when using Turbo.

Fixing HMR

When starting vite, all the correct CSS classes were being generated, but every subsequent change in my files only reloaded my modules, but not my Tailwind CSS.

My changes would only be picked up when restarting vite - so Tailwind had been configured just fine. Running Tailwind directly also confirmed the setup was fine since the correct files were being watched and the CSS compiled correctly:

npx @tailwindcss/cli -i ./apps/web/styles/tailwind.css -o ./apps/web/dist/out.css --watch

Running Tailwind V4 directly worked and showed the file changes were being picked up

Whenever I changed something in the tailwind.css file, Tailwind suddenly recompiled and everything worked.

This made me resort to writing a custom Vite plugin, that would simply touch the tailwind.css file on every change:

export default defineConfig({
    // ...
    plugins: [
        // ...
        {
            name: 'force-tailwind',
            handleHotUpdate({ file }) {
                console.log(`File changed: ${file}`);
                // Prevent infinite recursion
                if (file.endsWith('tailwind.css')) {
                    return;
                }
                const tailwindFile = resolve(__dirname, 'styles/tailwind.css');
                const newTime = new Date();
                fs.utimesSync(tailwindFile, newTime, newTime);
            }
        }
    ],
});

Hacky way to force tailwind to recompile when files change

And this actually worked pretty well, but left me unsatisfied because these kind of hacks will always haunt you in the future.

Actually fixing HMR

After some more digging the next day, the actual fix was pretty simple. Instead of using the @tailwindcss/vite plugin, I used the @tailwindcss/postcss plugin directly in Vite:

import tailwindcss from '@tailwindcss/postcss'

export default defineConfig({
    // ...
    css: {
        postcss: {
            plugins: [
                tailwindcss()
            ],
        }
    },
    // ...
});

And this immediately fixed my issues and allowed me to delete my hacky plugin I created the day prior.

Subscribe to my monthly newsletter

No spam, no sharing to third party. Only you and me.