</>CodeWithKarani

Tailwind v4 in Next.js: Fixing 'trying to use tailwindcss directly as a PostCSS plugin'

Karani GeoffreyKarani Geoffrey7 min read

You upgraded Tailwind, ran npm run dev, and Next.js refused to compile before it rendered a single pixel. The stack trace points at your CSS, not your code, which is the kind of error that makes you doubt the last three commands you ran. Nothing in your components changed. The build just stopped trusting tailwindcss.

This is the single most common wall people hit moving to Tailwind CSS v4, and it is not a bug in your project. In v4 the PostCSS plugin was pulled out of the main tailwindcss package into its own package, @tailwindcss/postcss. Every v3 tutorial, boilerplate and Stack Overflow answer that tells you to put tailwindcss: {} in your PostCSS config is now wrong, and the error you are reading is Tailwind telling you exactly that.

The fix is three small edits. Understanding why it happened will save you from the second and third errors waiting behind this one, because the v4 migration changes more than one file.

In Tailwind v4 the PostCSS plugin lives in a separate package. Install it and point your config at it.

  • Run npm install -D @tailwindcss/postcss
  • In postcss.config.mjs, replace tailwindcss: {} with '@tailwindcss/postcss': {} and delete the autoprefixer line (v4 handles it).
  • In your global CSS, replace the three @tailwind directives with a single @import "tailwindcss";
  • If it still fails, you have both v3 and v4 installed at once. Run npm ls tailwindcss and remove the stray version.

The exact error: "trying to use tailwindcss directly as a PostCSS plugin"

The full message, printed the moment Next.js tries to process your CSS, is:

It looks like you're trying to use `tailwindcss` directly as a PostCSS plugin. The PostCSS plugin has moved to a separate package, so to continue using Tailwind CSS with PostCSS you'll need to install `@tailwindcss/postcss` and update your PostCSS configuration.

Sometimes Next.js wraps it in a longer trace mentioning Error: Cannot apply unknown utility class or a failed module further down. Ignore the noise. If the phrase "moved to a separate package" appears anywhere in the output, this is your problem and nothing else is.

Why this happens: the plugin is no longer the package

In Tailwind v3, the tailwindcss npm package did two jobs at once. It was the engine that generated your utility classes, and it also exposed a PostCSS plugin interface so that PostCSS could call it. That is why the v3 config looked like this:

// postcss.config.js  (Tailwind v3 - do not use this in v4)
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

When PostCSS saw tailwindcss: {}, it imported the tailwindcss package and expected to find a PostCSS plugin sitting there. In v4 that plugin is gone from the main package. The Tailwind team rebuilt the engine on a new high-performance core (much of it in Rust) and split responsibilities so the framework integrations each live in their own package: @tailwindcss/postcss for PostCSS-based builds like Next.js, @tailwindcss/vite for Vite, and a standalone CLI.

So when your v3-shaped config asks PostCSS to load tailwindcss as a plugin, PostCSS finds the engine but no plugin interface. Tailwind detects that misuse and, instead of a cryptic type error, prints the friendly message telling you the plugin moved. It is one of the better-designed error messages in the ecosystem. The trouble is that thousands of published configs still show the old shape, so a fresh developer copies a two-year-old postcss.config.js and walks straight into it.

Tailwind v3 tailwindcss engine + PostCSS plugin (one package) postcss.config: { tailwindcss: {} } Tailwind v4 tailwindcss engine only @tailwindcss/ postcss the plugin Copying the v3 config into a v4 project loads the engine but finds no plugin. v4 config must name the new package: { '@tailwindcss/postcss': {} }
The engine did not disappear. The PostCSS entry point moved to a package your old config never mentions.

The fix, step by step for Next.js App Router

Step 1: Install the PostCSS package

From your project root:

npm install -D @tailwindcss/postcss

You do not need to uninstall tailwindcss. You still need the engine; you are just adding the plugin bridge next to it. Make sure tailwindcss itself is on v4:

npm ls tailwindcss

Expected output shows a single 4.x entry, for example tailwindcss@4.1.11. If you see a 3.x version, upgrade it with npm install -D tailwindcss@latest.

Step 2: Rewrite the PostCSS config

Next.js reads postcss.config.mjs (or .js) from the project root. Replace its contents so the plugin key is the new package and drop autoprefixer:

// postcss.config.mjs
const config = {
  plugins: {
    "@tailwindcss/postcss": {},
  },
};

export default config;

Two things people miss here. First, the plugin key is a quoted string because it contains a slash and an @. Second, you no longer list autoprefixer. Tailwind v4 runs vendor prefixing and nesting internally, so keeping autoprefixer in the list is at best redundant and at worst a source of double-prefixed CSS. Remove it and uninstall it if nothing else uses it.

Step 3: Switch the CSS entry file to a single import

In v3 your globals.css started with three directives:

/* v3 - remove these */
@tailwind base;
@tailwind components;
@tailwind utilities;

In v4 those become one line:

/* app/globals.css - v4 */
@import "tailwindcss";

If you leave the old @tailwind directives in place you will get a separate error about unknown at-rules once the plugin loads, so change this in the same pass rather than waiting for it to bite you.

Step 4: Move your config into CSS (the part that surprises people)

This is where the migration goes from a one-line fix to a real change. Tailwind v4 is CSS-first. Your theme customisations, custom colours, fonts and breakpoints no longer live in tailwind.config.js. They live in your CSS inside an @theme block:

/* app/globals.css */
@import "tailwindcss";

@theme {
  --color-brand: #1f7a4d;
  --font-display: "Inter", sans-serif;
  --breakpoint-3xl: 120rem;
}

That --color-brand line makes bg-brand, text-brand and friends real utility classes. You can still keep a JavaScript config for backwards compatibility by loading it explicitly with @config "../tailwind.config.js";, and that is a reasonable bridge for a large project mid-migration. But new projects should treat CSS as the source of truth. If you upgraded and half your custom colours vanished, this is why: they are still sitting in a tailwind.config.js that v4 no longer reads automatically.

Verification: prove the build is clean

Restart the dev server from a clean state so you are not reading a cached error:

rm -rf .next
npm run dev

A healthy start compiles with no CSS error and serves the page with styles applied. To confirm utilities are actually being generated rather than silently dropped, run a production build:

npm run build

Expected: the build completes, and a quick grep of the emitted CSS shows your classes. If a custom utility like bg-brand is missing from the output, the class is defined but your @theme token is not, which points you back to Step 4 rather than the PostCSS config.

What people get wrong

Downgrading back to v3 to "make the error go away." This works, and it is the wrong move if you started the upgrade on purpose. You will hit v4 again the next time you scaffold a project or pull a dependency that expects it. Spend the twenty minutes now.

Adding @tailwindcss/postcss but leaving tailwindcss: {} in the config too. Listing both plugins does not help. PostCSS still tries to load the old plugin entry point and still throws. There should be exactly one Tailwind entry in your plugins object, and it must be the new package.

Assuming the error means Tailwind is not installed. The message mentions installing a package, so people run npm install tailwindcss again and are baffled when nothing changes. The engine was never missing. Read the sentence to the end: it asks for @tailwindcss/postcss, a different package.

When it is still broken: the version-mismatch trap

If you did all four steps and the identical error still appears, you almost certainly have two versions of Tailwind installed at once, usually because a dependency or a starter template pins v3 transitively. The v4 plugin refuses to run against a v3 engine and prints the same message.

Diagnose it:

npm ls tailwindcss

If the tree shows both a 3.x and a 4.x entry, find what pulls in the old one, deduplicate, and delete the lockfile and node_modules if the tree is tangled:

rm -rf node_modules package-lock.json
npm install

In a monorepo the mismatch is often that the root and a workspace each declare a different major version. If you keep seeing dependency resolution surprises across a workspace, the same root cause shows up elsewhere; I wrote about one flavour of it in Next.js 'Found multiple lockfiles'. And if your build fails for reasons that turn out to be memory rather than config, JavaScript Heap Out of Memory on next build covers that path.

For the canonical details on the theme system and the @config escape hatch, the official Tailwind v4 upgrade guide is worth ten minutes once the build is green again.

Frequently asked questions

Do I have to uninstall tailwindcss when I install @tailwindcss/postcss?
No. The tailwindcss package is still the engine that generates your utility classes and you need it. You only add @tailwindcss/postcss alongside it as the PostCSS bridge, then point your postcss config at the new package instead of at tailwindcss directly.
Where did tailwind.config.js go in Tailwind v4?
Tailwind v4 is CSS-first, so theme values now live in an @theme block inside your CSS rather than in tailwind.config.js. You can still load a JavaScript config explicitly with @config "./tailwind.config.js"; as a migration bridge, but v4 no longer reads it automatically, which is why custom colours often disappear after upgrading.
Why do I still get the error after installing @tailwindcss/postcss?
Almost always because two versions of tailwindcss are installed at once, usually a v3 pulled in transitively by a template or dependency. Run npm ls tailwindcss; if you see both a 3.x and a 4.x, deduplicate, and if needed delete node_modules and the lockfile and reinstall.
Do I still need autoprefixer with Tailwind v4?
No. Tailwind v4 handles vendor prefixing and CSS nesting internally, so you should remove the autoprefixer entry from your PostCSS plugins and can uninstall it if nothing else uses it. Leaving it in can produce redundant double-prefixed output.
#Tailwind CSS#Next.js#PostCSS#CSS#Build Tools
Keep reading

Related articles