Skip to content

Commit e3c531c

Browse files
committed
feat(skill): tailwind-impl-build-nextjs
1 parent 976e834 commit e3c531c

4 files changed

Lines changed: 1448 additions & 0 deletions

File tree

Lines changed: 354 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,354 @@
1+
---
2+
name: tailwind-impl-build-nextjs
3+
description: >
4+
Use when installing or wiring Tailwind CSS into a Next.js project, for
5+
either the App Router or the Pages Router, on Tailwind v3 or v4, and when
6+
hooking next/font/google families into the Tailwind theme so utilities
7+
like font-sans resolve to the loaded family. Prevents the four most
8+
common Next-js install traps: importing globals.css in the wrong file
9+
for the active router, forgetting the @tailwindcss/postcss plugin under
10+
v4, breaking the v3 content glob on catch-all route folders named
11+
[...slug], and pasting font CSS variables into next/font/local without
12+
declaring them inside @theme inline so they never become utilities.
13+
Covers v4 App Router with @import tailwindcss, v4 Pages Router, v3 App
14+
Router with @tailwind directives, v3 Pages Router, next/font wiring via
15+
className on html plus @theme inline { --font-sans: var(--font-inter) },
16+
why React Server Components have zero runtime cost from Tailwind, and
17+
the Turbopack arbitrary-value miss workaround for Next 16 plus v4 from
18+
tailwindlabs/tailwindcss issue 19825.
19+
Keywords: tailwind nextjs, tailwind next-js, install tailwind next,
20+
next app router tailwind, next pages router tailwind, app/globals-css,
21+
pages/_app-tsx, @tailwindcss/postcss, postcss-config-mjs, tailwind v4
22+
next, tailwind v3 next, @import tailwindcss next, @tailwind directives
23+
next, tailwind init -p next, next/font google tailwind, next/font local
24+
tailwind, --font-inter @theme inline, font-sans next/font, RSC
25+
tailwind, server components tailwind, no runtime cost styling,
26+
turbopack arbitrary value miss, aspect-[12/5] missing turbopack,
27+
z-[100] missing, next 16 tailwind v4 issue 19825, catch-all route
28+
[...slug] glob, @source './[[]**[]]', how do I add tailwind to next,
29+
my tailwind classes are not applying in nextjs, blank styles next,
30+
why doesn't dark mode work next, getting started tailwind next.
31+
license: MIT
32+
compatibility: "Designed for Claude Code. Requires Tailwind CSS v3.4 or v4.0+."
33+
metadata:
34+
author: OpenAEC-Foundation
35+
version: "1.0"
36+
---
37+
38+
# Tailwind CSS in Next.js : Install and Wire-up
39+
40+
Covers every supported combination of Tailwind version (v3.4 vs v4.0+) and
41+
Next.js router (App Router vs Pages Router), plus next/font integration
42+
and the documented Turbopack quirk.
43+
44+
Companion skills :
45+
46+
- `tailwind-impl-config-v4` : v4 CSS-first config surface (@theme, @source)
47+
- `tailwind-impl-config-v3` : v3 JS config surface (tailwind.config.js)
48+
- `tailwind-impl-build-vite` : same install topic for the Vite plugin
49+
- `tailwind-core-v3-vs-v4` : version-wide API differences
50+
51+
## Decision : Which Combination Do You Have
52+
53+
| Tailwind version | Router | Entry CSS lives in | Imported from |
54+
|------------------|--------|--------------------|---------------|
55+
| v4 | App Router | `app/globals.css` | `app/layout.tsx` |
56+
| v4 | Pages Router | `styles/globals.css` | `pages/_app.tsx` |
57+
| v3 | App Router | `app/globals.css` | `app/layout.tsx` |
58+
| v3 | Pages Router | `styles/globals.css` | `pages/_app.tsx` |
59+
60+
The CSS file location is identical across versions. Only the file
61+
contents differ : `@import "tailwindcss";` for v4, three `@tailwind`
62+
directives for v3.
63+
64+
## Minimum Setup : Tailwind v4 + Next.js App Router
65+
66+
```bash
67+
npm install tailwindcss @tailwindcss/postcss postcss
68+
```
69+
70+
`postcss.config.mjs` (project root) :
71+
72+
```js
73+
const config = {
74+
plugins: {
75+
"@tailwindcss/postcss": {},
76+
},
77+
};
78+
export default config;
79+
```
80+
81+
`app/globals.css` :
82+
83+
```css
84+
@import "tailwindcss";
85+
```
86+
87+
`app/layout.tsx` :
88+
89+
```tsx
90+
import "./globals.css";
91+
92+
export default function RootLayout({ children }: { children: React.ReactNode }) {
93+
return (
94+
<html lang="en">
95+
<body>{children}</body>
96+
</html>
97+
);
98+
}
99+
```
100+
101+
That is the complete v4 + App Router install. No `tailwind.config.js`, no
102+
`@tailwind` directives, no `autoprefixer`, no `postcss-import`. The
103+
`@tailwindcss/postcss` plugin runs the Oxide engine which bundles all
104+
three.
105+
106+
## Minimum Setup : Tailwind v4 + Next.js Pages Router
107+
108+
Install and PostCSS config are identical to the App Router setup above.
109+
Only the import location moves :
110+
111+
`styles/globals.css` :
112+
113+
```css
114+
@import "tailwindcss";
115+
```
116+
117+
`pages/_app.tsx` :
118+
119+
```tsx
120+
import "@/styles/globals.css";
121+
import type { AppProps } from "next/app";
122+
123+
export default function App({ Component, pageProps }: AppProps) {
124+
return <Component {...pageProps} />;
125+
}
126+
```
127+
128+
## Minimum Setup : Tailwind v3 + Next.js App Router
129+
130+
```bash
131+
npm install -D tailwindcss@3 postcss autoprefixer
132+
npx tailwindcss init -p
133+
```
134+
135+
`tailwindcss init -p` generates two files :
136+
137+
`tailwind.config.js` :
138+
139+
```js
140+
/** @type {import('tailwindcss').Config} */
141+
module.exports = {
142+
content: [
143+
"./app/**/*.{js,ts,jsx,tsx,mdx}",
144+
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
145+
"./components/**/*.{js,ts,jsx,tsx,mdx}",
146+
"./src/**/*.{js,ts,jsx,tsx,mdx}",
147+
],
148+
theme: { extend: {} },
149+
plugins: [],
150+
};
151+
```
152+
153+
`postcss.config.js` :
154+
155+
```js
156+
module.exports = {
157+
plugins: {
158+
tailwindcss: {},
159+
autoprefixer: {},
160+
},
161+
};
162+
```
163+
164+
`app/globals.css` :
165+
166+
```css
167+
@tailwind base;
168+
@tailwind components;
169+
@tailwind utilities;
170+
```
171+
172+
`app/layout.tsx` : identical to the v4 case, just `import "./globals.css";`.
173+
174+
## Minimum Setup : Tailwind v3 + Next.js Pages Router
175+
176+
Same install, same `tailwind.config.js`, same `postcss.config.js`. The CSS
177+
file lives at `styles/globals.css` (same three `@tailwind` directives),
178+
imported once in `pages/_app.tsx` :
179+
180+
```tsx
181+
import "@/styles/globals.css";
182+
import type { AppProps } from "next/app";
183+
184+
export default function App({ Component, pageProps }: AppProps) {
185+
return <Component {...pageProps} />;
186+
}
187+
```
188+
189+
## Wire next/font/google Into the Tailwind Theme
190+
191+
next/font emits a CSS variable that you opt into by applying a className.
192+
Tailwind reads that variable as a token via `@theme` (v4) or
193+
`fontFamily.extend` (v3).
194+
195+
### v4 pattern
196+
197+
`app/layout.tsx` :
198+
199+
```tsx
200+
import { Inter } from "next/font/google";
201+
import "./globals.css";
202+
203+
const inter = Inter({
204+
subsets: ["latin"],
205+
variable: "--font-inter",
206+
});
207+
208+
export default function RootLayout({ children }: { children: React.ReactNode }) {
209+
return (
210+
<html lang="en" className={inter.variable}>
211+
<body className="font-sans">{children}</body>
212+
</html>
213+
);
214+
}
215+
```
216+
217+
`app/globals.css` :
218+
219+
```css
220+
@import "tailwindcss";
221+
222+
@theme inline {
223+
--font-sans: var(--font-inter), ui-sans-serif, system-ui, sans-serif;
224+
}
225+
```
226+
227+
ALWAYS use `@theme inline { ... }` (not bare `@theme { ... }`) when the
228+
token value contains a `var(...)` reference to another CSS variable that
229+
exists at runtime. Bare `@theme` resolves the variable at build time and
230+
emits the resolved literal, which is empty before next/font runs and
231+
strips the family.
232+
233+
### v3 pattern
234+
235+
`pages/_app.tsx` (or `app/layout.tsx`) wires the className the same way.
236+
The config file declares the family :
237+
238+
```js
239+
// tailwind.config.js
240+
module.exports = {
241+
content: [/* unchanged */],
242+
theme: {
243+
extend: {
244+
fontFamily: {
245+
sans: ["var(--font-inter)", "ui-sans-serif", "system-ui", "sans-serif"],
246+
},
247+
},
248+
},
249+
};
250+
```
251+
252+
## React Server Components : Zero Runtime Cost
253+
254+
Tailwind is a build-time compiler. It scans source files for class
255+
tokens, generates a single static CSS bundle, and injects that bundle via
256+
the same `<link rel="stylesheet">` Next.js uses for every other CSS
257+
import. There is no client runtime, no Server Component restriction, no
258+
`"use client"` boundary needed for styling. RSC, Client Components, and
259+
Server Actions all consume the same CSS.
260+
261+
Consequences :
262+
263+
- NEVER move a Server Component to Client just to use Tailwind classes.
264+
- ALWAYS apply Tailwind classes directly on RSC output ; no wrapper.
265+
- Bundle size scales with the number of utilities used, not with the
266+
number of components. Adding a hundred RSC components that share the
267+
same utilities adds zero CSS bytes.
268+
269+
## Turbopack Arbitrary-Value Miss (Issue 19825)
270+
271+
Affects Next.js 16.1.6 + Tailwind v4.2.1 + `next dev --turbo` (and
272+
`next dev` once Turbopack becomes the default). Classes with arbitrary
273+
square-bracket values render in the DOM but the corresponding CSS rule
274+
is missing from the injected stylesheet.
275+
276+
Reproduced tokens : `aspect-[12/5]`, `z-[100]`, `h-[80vh]`. The same
277+
classes compile correctly under webpack (`next dev` without `--turbo`).
278+
279+
### Workarounds
280+
281+
1. **Inline style for layout-critical values** :
282+
```tsx
283+
<div style={{ aspectRatio: "12/5" }} className="w-full bg-zinc-900" />
284+
```
285+
2. **Safelist the exact token via `@source inline`** so the scanner is
286+
forced to emit the rule regardless of Turbopack's incremental scan :
287+
```css
288+
@source inline("aspect-[12/5] z-[100] h-[80vh]");
289+
```
290+
3. **Disable Turbopack for the affected build** : remove `--turbo` from
291+
`next dev` until the upstream fix lands.
292+
293+
NEVER ship `aspect-[12/5]` style arbitrary values to a Turbopack
294+
production build without first verifying the rule appears in
295+
`.next/static/css`. The DOM showing the class is not proof the CSS
296+
exists.
297+
298+
## Catch-All Route Glob Trap (Tailwind v3)
299+
300+
The default v3 content glob `./app/**/*.{js,ts,jsx,tsx,mdx}` treats
301+
square brackets as a character class. Files inside a folder named
302+
`[...slug]` or `[id]` are silently excluded from the scan. Symptom :
303+
classes on dynamic-route pages produce no CSS.
304+
305+
Fix (v3) : add a second content entry that escapes the brackets, or
306+
list the catch-all folder explicitly :
307+
308+
```js
309+
content: [
310+
"./app/**/*.{js,ts,jsx,tsx,mdx}",
311+
"./app/[[]**[]]/*.{js,ts,jsx,tsx,mdx}",
312+
],
313+
```
314+
315+
Fix (v4) : add an explicit `@source` :
316+
317+
```css
318+
@import "tailwindcss";
319+
@source "./app/[[]**[]]/**/*.{js,ts,jsx,tsx,mdx}";
320+
```
321+
322+
## Verification Checklist
323+
324+
1. CSS file imports exactly once (root layout for App Router, `_app.tsx`
325+
for Pages Router). Importing twice doubles the bundle.
326+
2. `npm run dev` shows utilities applying within one second on hot
327+
reload.
328+
3. Dynamic-route pages (any folder with `[id]`, `[...slug]`,
329+
`[[...slug]]`) render utilities correctly. If not, the bracket-glob
330+
trap is active.
331+
4. If next/font is wired : a token like `text-3xl` renders with the
332+
loaded family, not the browser fallback. Inspect computed
333+
`font-family` in DevTools.
334+
5. Production : `npm run build` finishes without `PostCSS plugin
335+
tailwindcss requires PostCSS 8` (v3 only ; install `postcss@8`).
336+
6. Turbopack : if `next dev --turbo`, confirm arbitrary-value classes
337+
actually emit CSS, not just DOM attributes.
338+
339+
## References
340+
341+
- `references/methods.md` : full per-router install walkthroughs with
342+
every file and exact contents
343+
- `references/examples.md` : working `app/layout.tsx`, `pages/_app.tsx`,
344+
fonts wiring, mixed-mode setups
345+
- `references/anti-patterns.md` : the eight Next.js-specific traps with
346+
symptoms, root cause, fix, and verification
347+
348+
## Sources
349+
350+
- https://tailwindcss.com/docs/installation/framework-guides/nextjs
351+
- https://v3.tailwindcss.com/docs/guides/nextjs
352+
- https://github.com/tailwindlabs/tailwindcss/issues/19825
353+
- https://github.com/tailwindlabs/tailwindcss/issues/16287
354+
- https://nextjs.org/docs/app/api-reference/components/font

0 commit comments

Comments
 (0)