1

I am trying to deploy an Astro.js site on Vercel that utilizes TailwindCSS v4:

tailwind.config.js

/** @type {import('tailwindcss').Config} */
export default {
  purge: ["./src/pages/**/*.{js,ts,jsx,tsx}", "./src/Components/**/*. {js,ts,jsx,tsx}"],
  content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
  theme: {
    extend: {},
  },
  plugins: [],
};

astro.config.js

// @ts-check
import { defineConfig, fontProviders } from 'astro/config';
import playformCompress from '@playform/compress';
import react from "@astrojs/react";
import vercel from '@astrojs/vercel';
import tailwindcss from "@tailwindcss/vite";

// https://astro.build/config
export default defineConfig({
  prefetch: {
    prefetchAll: false
  },
  vite: {
    css: {
      transformer: "lightningcss"
    },
    build: {
      cssMinify: 'lightningcss'
    },
    plugins: [tailwindcss()]
  },
  integrations: [react(), playformCompress()],
  experimental: {
    fonts: [{
      provider: fontProviders.google(),
      name: "Geist",
      cssVariable: "--font-geist",
      fallbacks: ["Inter", "sans-serif"],
    }]
  },
  output: 'server',
  adapter: vercel(),
});

On a local build using npm run build works perfectly fine in every case. On Vercel I set the following commands for dependency install and build:

npm install
npm run build

I set the default output directory to ./dist. Whenever I deploy to Vercel on production Tailwind seems to break completely and I am not sure why there would be a difference. When I run local build I do notice that static files are placed in .vercel/output.static/, but I am not sure if this exactly makes a difference as I have tried setting the default output path to there and I still see the same broken CSS.

2 Answers 2

1

Handle build with Vite when using TailwindCSS v4

As cr33d pointed out in them answer, TailwindCSS handles the build process on its own, so there's no need for additional CSS-related settings in the vite.build object. In this case, you should remove the use of vite.build.cssMinify, so that TailwindCSS continues to generate the CSS instead of LightningCSS.

If you’re using Astro, change astro.config.js

// @ts-check
import { defineConfig } from "astro/config";
import tailwindcss from "@tailwindcss/vite";

export default defineConfig({
  vite: {
    css: {
      transformer: "lightningcss"
    },
    build: {
      // do not override TailwindCSS, a void using cssMinify
      // cssMinify: 'lightningcss'
    },
    plugins: [tailwindcss()]
  },
}

If you're using another Vite project, change vite.config.js

import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'

export default defineConfig({
  css: {
    transformer: "lightningcss"
  },
  build: {
    // do not override TailwindCSS, a void using cssMinify
    // cssMinify: 'lightningcss'
  },
  plugins: [tailwindcss()]
}

Note: Under the hood, TailwindCSS v4 already uses LightningCSS for the build, so that part remains unchanged in the background. This way, TailwindCSS can properly handle its own custom syntax.

Configuration from TailwindCSS v4

From TailwindCSS v4, a new CSS-first configuration was introduced, so tailwind.config.js is no longer needed and can be deleted.

So your new configuration looks something like this: global.css

@import "tailwindcss";

@source "./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}";

@theme {
  /* ... */
}

The @config was created so that anyone migrating from v3 to v4 would have the option to get their app running quickly with minimal changes, but many breaking changes have occurred in the meantime, which you should review:

Other related:

Sign up to request clarification or add additional context in comments.

Comments

1

Resolved the issue by changing:

export default defineConfig({
  prefetch: {
    prefetchAll: false
  },
  vite: {
    css: {
      transformer: "lightningcss"
    },
    build: {
      cssMinify: 'lightningcss'
    },
    plugins: [tailwindcss()]
  },
  integrations: [react(), playformCompress()],
  experimental: {
    fonts: [{
      provider: fontProviders.google(),
      name: "Geist",
      cssVariable: "--font-geist",
      fallbacks: ["Inter", "sans-serif"],
    }]
  },
  output: 'server',
  adapter: vercel(),
});

To:

export default defineConfig({
  prefetch: {
    prefetchAll: false
  },
  vite: {
    css: {
      transformer: "lightningcss"
    },
    plugins: [tailwindcss()]
  },
  integrations: [react(), playformCompress()],
  experimental: {
    fonts: [{
      provider: fontProviders.google(),
      name: "Geist",
      cssVariable: "--font-geist",
      fallbacks: ["Inter", "sans-serif"],
    }]
  },
  output: 'server',
  adapter: vercel(),
});

What happens in ViteJs is that build will override the default behavior of astro build, thus the css minifier will override the default behavior of playformCompress(), and break the TailwindCSS.

1 Comment

Good point; nevertheless, it would be worthwhile to also review automatic source detection and the use of @source.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.