SuperAPI AI Directory

tailwind-impl-build-nextjs

Frontend & websites

View repository

Updated 10-15 22:18

About this skill

Tailwind CSS in Next.js : Install and Wire-up

Covers every supported combination of Tailwind version (v3.4 vs v4.0+) and Next.js router (App Router vs Pages Router), plus next/font integration and the documented Turbopack quirk.

Companion skills :

  • tailwind-impl-config-v4 : v4 CSS-first config surface (@theme, @source)
  • tailwind-impl-config-v3 : v3 JS config surface (tailwind.config.js)
  • tailwind-impl-build-vite : same install topic for the Vite plugin
  • tailwind-core-v3-vs-v4 : version-wide API differences

Decision : Which Combination Do You Have

Tailwind version Router Entry CSS lives in Imported from
v4 App Router app/globals.css app/layout.tsx
v4 Pages Router styles/globals.css pages/_app.tsx
v3 App Router app/globals.css app/layout.tsx
v3 Pages Router styles/globals.css pages/_app.tsx

The CSS file location is identical across versions. Only the file contents differ : @import "tailwindcss"; for v4, three @tailwind directives for v3.

Minimum Setup : Tailwind v4 + Next.js App Router

npm install tailwindcss @tailwindcss/postcss postcss

postcss.config.mjs (project root) :

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

app/globals.css :

@import "tailwindcss";

app/layout.tsx :

import "./globals.css";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

That is the complete v4 + App Router install. No tailwind.config.js, no @tailwind directives, no autoprefixer, no postcss-import. The @tailwindcss/postcss plugin runs the Oxide engine which bundles all three.

Minimum Setup : Tailwind v4 + Next.js Pages Router

Install and PostCSS config are identical to the App Router setup above. Only the import location moves :

styles/globals.css :

@import "tailwindcss";

pages/_app.tsx :

import "@/styles/globals.css";
import type { AppProps } from "next/app";

export default function App({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />;
}

Minimum Setup : Tailwind v3 + Next.js App Router

npm install -D tailwindcss@3 postcss autoprefixer
npx tailwindcss init -p

tailwindcss init -p generates two files :

tailwind.config.js :

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
    "./pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./components/**/*.{js,ts,jsx,tsx,mdx}",
    "./src/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: { extend: {} },
  plugins: [],
};

postcss.config.js :

module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
};

app/globals.css :

@tailwind base;
@tailwind components;
@tailwind utilities;

app/layout.tsx : identical to the v4 case, just import "./globals.css";.

Minimum Setup : Tailwind v3 + Next.js Pages Router

Same install, same tailwind.config.js, same postcss.config.js. The CSS file lives at styles/globals.css (same three @tailwind directives), imported once in pages/_app.tsx :

import "@/styles/globals.css";
import type { AppProps } from "next/app";

export default function App({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />;
}

Wire next/font/google Into the Tailwind Theme

next/font emits a CSS variable that you opt into by applying a className. Tailwind reads that variable as a token via @theme (v4) or fontFamily.extend (v3).

v4 pattern

app/layout.tsx :

import { Inter } from "next/font/google";
import "./globals.css";

const inter = Inter({
  subsets: ["latin"],
  variable: "--font-inter",
});

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" className={inter.variable}>
      <body className="font-sans">{children}</body>
    </html>
  );
}

app/globals.css :

@import "tailwindcss";

@theme inline {
  --font-sans: var(--font-inter), ui-sans-serif, system-ui, sans-serif;
}

ALWAYS use @theme inline { ... } (not bare @theme { ... }) when the token value contains a var(...) reference to another CSS variable that exists at runtime. Bare @theme resolves the variable at build time and emits the resolved literal, which is empty before next/font runs and strips the family.

v3 pattern

pages/_app.tsx (or app/layout.tsx) wires the className the same way. The config file declares the family :

// tailwind.config.js
module.exports = {
  content: [/* unchanged */],
  theme: {
    extend: {
      fontFamily: {
        sans: ["var(--font-inter)", "ui-sans-serif", "system-ui", "sans-serif"],
      },
    },
  },
};

React Server Components : Zero Runtime Cost

Tailwind is a build-time compiler. It scans source files for class tokens, generates a single static CSS bundle, and injects that bundle via the same <link rel="stylesheet"> Next.js uses for every other CSS import. There is no client runtime, no Server Component restriction, no "use client" boundary needed for styling. RSC, Client Components, and Server Actions all consume the same CSS.

Consequences :

  • NEVER move a Server Component to Client just to use Tailwind classes.
  • ALWAYS apply Tailwind classes directly on RSC output ; no wrapper.
  • Bundle size scales with the number of utilities used, not with the number of components. Adding a hundred RSC components that share the same utilities adds zero CSS bytes.

Turbopack Arbitrary-Value Miss (Issue 19825)

Affects Next.js 16.1.6 + Tailwind v4.2.1 + next dev --turbo (and next dev once Turbopack becomes the default). Classes with arbitrary square-bracket values render in the DOM but the corresponding CSS rule is missing from the injected stylesheet.

Reproduced tokens : aspect-[12/5], z-[100], h-[80vh]. The same classes compile correctly under webpack (next dev without --turbo).

Workarounds

  1. Inline style for layout-critical values :
    <div style={{ aspectRatio: "12/5" }} className="w-full bg-zinc-900" />
    
  2. Safelist the exact token via @source inline so the scanner is forced to emit the rule regardless of Turbopack's incremental scan :
    @source inline("aspect-[12/5] z-[100] h-[80vh]");
    
  3. Disable Turbopack for the affected build : remove --turbo from next dev until the upstream fix lands.

NEVER ship aspect-[12/5] style arbitrary values to a Turbopack production build without first verifying the rule appears in .next/static/css. The DOM showing the class is not proof the CSS exists.

Catch-All Route Glob Trap (Tailwind v3)

The default v3 content glob ./app/**/*.{js,ts,jsx,tsx,mdx} treats square brackets as a character class. Files inside a folder named [...slug] or [id] are silently excluded from the scan. Symptom : classes on dynamic-route pages produce no CSS.

Fix (v3) : add a second content entry that escapes the brackets, or list the catch-all folder explicitly :

content: [
  "./app/**/*.{js,ts,jsx,tsx,mdx}",
  "./app/[[]**[]]/*.{js,ts,jsx,tsx,mdx}",
],

Fix (v4) : add an explicit @source :

@import "tailwindcss";
@source "./app/[[]**[]]/**/*.{js,ts,jsx,tsx,mdx}";

Verification Checklist

  1. CSS file imports exactly once (root layout for App Router, _app.tsx for Pages Router). Importing twice doubles the bundle.
  2. npm run dev shows utilities applying within one second on hot reload.
  3. Dynamic-route pages (any folder with [id], [...slug], [[...slug]]) render utilities correctly. If not, the bracket-glob trap is active.
  4. If next/font is wired : a token like text-3xl renders with the loaded family, not the browser fallback. Inspect computed font-family in DevTools.
  5. Production : npm run build finishes without PostCSS plugin tailwindcss requires PostCSS 8 (v3 only ; install postcss@8).
  6. Turbopack : if next dev --turbo, confirm arbitrary-value classes actually emit CSS, not just DOM attributes.

References

  • references/methods.md : full per-router install walkthroughs with every file and exact contents
  • references/examples.md : working app/layout.tsx, pages/_app.tsx, fonts wiring, mixed-mode setups
  • references/anti-patterns.md : the eight Next.js-specific traps with symptoms, root cause, fix, and verification

Sources