Styling — CSS Modules, Tailwind & Fonts
Global CSS
Imported once in the root layout — resets, tokens, base typography:
// app/layout.js
import "./globals.css";/* app/globals.css */
:root {
--bg: #0A0A14;
--accent: #6366F1;
}
body {
background: var(--bg);
font-family: var(--font-inter); /* set by next/font below */
}CSS Modules — Scoped by Default
Files named *.module.css generate unique class names — styles can’t leak between components:
/* components/Card.module.css */
.card {
background: #111827;
border-radius: 16px;
padding: 2rem;
}
.card:hover { border-color: var(--accent); }
.title { font-weight: 700; }import styles from "./Card.module.css";
export default function Card({ title, children }) {
return (
<div className={styles.card}> {/* → "Card_card__x7Ab3" */}
<h3 className={styles.title}>{title}</h3>
{children}
</div>
);
}Conditional classes: className={`${styles.card} ${featured ? styles.featured : ""}`} (or the tiny clsx package).
Tailwind CSS
The most popular choice in the Next ecosystem — utility classes straight in JSX, no separate files. create-next-app offers it during setup:
export default function Card({ title }) {
return (
<div className="rounded-2xl border border-gray-800 bg-gray-900 p-8
transition hover:border-indigo-500">
<h3 className="text-lg font-bold">{title}</h3>
</div>
);
}Trade-off in one line: Tailwind colocates styling with markup and eliminates naming; CSS Modules keep classic CSS with guaranteed scoping. Both are first-class — pick per project, not per file. (The CSS knowledge from the CSS series applies fully either way; Tailwind is CSS with shorter names.)
Fonts with next/font — No Layout Shift
Self-hosts Google fonts at build time — no external requests, no flash of fallback text:
// app/layout.js
import { Inter, JetBrains_Mono } from "next/font/google";
const inter = Inter({
subsets: ["latin"],
variable: "--font-inter",
});
const mono = JetBrains_Mono({
subsets: ["latin"],
variable: "--font-mono",
});
export default function RootLayout({ children }) {
return (
<html lang="en" className={`${inter.variable} ${mono.variable}`}>
<body>{children}</body>
</html>
);
}Now var(--font-inter) works in any CSS, and the font files ship from your own domain with optimal caching.
Conditional & Dynamic Styling Notes
- Server components can compute class names from data — zero JS cost:
className={post.featured ? "card featured" : "card"}. - Avoid styled-components/emotion in the App Router unless you must — runtime CSS-in-JS fights server components. CSS Modules and Tailwind are the supported happy paths.