next.jstailwind-cssvitesttailwind-css-4

Invalid PostCSS Plugin found using TailwindCSS 4 and Vitest


I have a Next.js app that is running without any errors. However, I am using Vitest for my testing framework and when running a specific test (layout.test.tsx) it produces this error:

 FAIL  src/app/__tests__/layout.test.tsx [ src/app/__tests__/layout.test.tsx ]
Failed to load PostCSS config: Failed to load PostCSS config (searchPath: /Users/joe/my-app): [TypeError] Invalid PostCSS Plugin found at: plugins[0]

(@/Users/joe/my-app/postcss.config.mjs)
TypeError: Invalid PostCSS Plugin found at: plugins[0]

(@/Users/joe/my-app/postcss.config.mjs)
    at file:///Users/joe/my-app/node_modules/vite/dist/node/chunks/dep-DBxKXgDP.js:11827:15
    at Array.forEach (<anonymous>)
    at plugins (file:///Users/joe/my-app/node_modules/vite/dist/node/chunks/dep-DBxKXgDP.js:11809:10)
    at processResult (file:///Users/joe/my-app/node_modules/vite/dist/node/chunks/dep-DBxKXgDP.js:11876:20)
  Plugin: vite:css
  File: /Users/joe/my-app/src/app/globals.css

I have no idea what is causing this error and have search all over the internet and followed various suggestions. Here are some of the key files of the project:

postcss.config.mjs

const config = {
  plugins: ["@tailwindcss/postcss"],
};

export default config;

vitest.config.mjs

import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";
import tsconfigPaths from "vite-tsconfig-paths";

export default defineConfig({
  plugins: [tsconfigPaths(), react()],
  test: {
    environment: "jsdom",
    globals: true,
    setupFiles: "./vitest.setup.mjs",
  },
});

global.css

@import "tailwindcss";

:root {
  --background: #ffffff;
  --foreground: #171717;
}

@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --font-sans: var(--font-geist-sans);
}

@media (prefers-color-scheme: dark) {
  :root {
    --background: #0a0a0a;
    --foreground: #ededed;
  }
}

body {
  background: var(--background);
  color: var(--foreground);
  font-family: Arial, Helvetica, sans-serif;
}

layout.tsx

import type { Metadata } from "next";
import { Geist } from "next/font/google";
import "./globals.css";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body className={`${geistSans.variable} antialiased`}>{children}</body>
    </html>
  );
}

layout.test.tsx

import { render, screen } from "@testing-library/react";
import RootLayout from "../layout";

describe("<RootLayout />", () => {
  describe("WHEN the component is rendered", () => {
    beforeEach(() => {
      render(
        <RootLayout>
          <div>Some content</div>
        </RootLayout>,
      );
    });

    test("THEN the child component is displayed", () => {
      const content = screen.getByText("Some content");
      expect(content).toBeVisible();
    });
  });
});

Solution

  • Your PostCSS configuration is incorrect. It seems that in one of Next.js's pull requests, they fixed the PostCSS configuration in one of their Tailwind templates according to the response, but not all templates have been updated.

    PostCSS Configuration

    If plugins is an array, it expects functions.

    postcss.config.mjs

    import tailwind from "@tailwindcss/postcss";
    
    const config = {
      plugins: [
        tailwind(),
      ],
    };
    
    export default config;
    

    If it is an object, the key defines the plugin, and additional options can be passed like this:

    postcss.config.mjs

    const config = {
      plugins: {
        "@tailwindcss/postcss": {},
      },
    };
    
    export default config;
    

    Extra note: For TailwindCSS v4, it's important to use .mjs. Also, make sure your project is set to module type in the package.json.

    Alternative

    If the postcss.config.mjs feels like a burden, you can also declare it in your package.json:

    {
      "postcss": {
        "plugins": {
          "@tailwindcss/postcss": {}
        }
      }
    }