Styling — Dark Theme

Website Rebuild with Next.js

Chapter 5 · Styling — Dark Theme

A deliberately light chapter, like every sibling course's own equivalent — two built-in mechanisms, applied directly to a real project rather than re-derived from scratch: a global stylesheet for the site's own dark theme, and CSS Modules for the breadcrumb's own scoped styling.

The Global Dark Theme

/* app/globals.css */ :root { --accent: #4DB8C8; --bg: #0d1117; --text: #c9d1d9; } body { background: var(--bg); color: var(--text); font-family: system-ui, sans-serif; }
// app/layout.tsx — updated from Chapter 4 import './globals.css'; export default function RootLayout({ children }: { children: React.ReactNode }) { // ...rest unchanged from Chapter 4 }

Imported once, in the layout every route already renders inside of automatically — no per-page import needed anywhere, the same structural benefit Chapter 4's own layout finding already established.

CSS Modules for the Breadcrumb

/* components/PageShell.module.css */ .breadcrumb { display: flex; gap: 0.5rem; padding: 0.75rem 0; } .breadcrumb a { color: #8b949e; text-decoration: none; } .breadcrumb a:hover { color: var(--accent); }
// components/PageShell.tsx — updated from Chapter 4 import styles from './PageShell.module.css'; // ... <nav className={styles.breadcrumb}> {breadcrumb.map((crumb) => ( <a key={crumb.id} href={`/${crumb.fullPath}`}>{crumb.title}</a> ))} </nav>

styles.breadcrumb isn't the literal class name "breadcrumb" — Next.js's own build step rewrites it into a unique, hashed class name (something like PageShell_breadcrumb__a1b2c) automatically, so it can never collide with an identically-named class anywhere else in the project, no manual naming convention needed.

Automatic Bundling, Verified Directly

Confirming a claim the series' own comparison table already makes about this course
Express Rebuild's own Chapter 5 already credits Next.js with "Bundles by design" and "Yes, automatic" cache-busting — written before this chapter itself existed to confirm it. Running next build and inspecting .next/static/css/ shows exactly that: the compiled global stylesheet and every CSS Module both come out as content-hashed filenames (e.g. a1b2c3d4.css), with zero configuration required to get there. If the file's own content ever changes, the hash changes with it — a genuinely automatic cache-busting guarantee, not something this course had to wire up by hand.

The Full Asset-Bundling Spectrum, Across the Series

Next.jsDjangoLaravelRailsAstroExpress
ApproachBundles by designShips nothingPre-wires VitePropshaft + ImportmapScoped styles + plain importexpress.static()
Cache-busting?Yes, automaticNoYes, via ViteYes — content-hash fingerprintingBuild-time hashingNone — manual only
Scoping mechanismCSS Modules — hashed class namesNone built inNone built inNone built inNative scoped <style> blocksNone — plain global CSS

Hands-On Exercises

Exercise 1

Add app/globals.css with the dark theme applied via the root layout's own import, and confirm it applies site-wide across every route.

📄 View solution
Exercise 2

Add a second, unrelated component with its own nav element and its own .breadcrumb class name in a plain (non-Module) CSS file, and confirm PageShell's own CSS-Module-scoped styles never leak into it, and vice versa.

📄 View solution
Exercise 3

Run next build, locate the compiled CSS output under .next/static/css/, and confirm the filename is content-hashed. Change a single style rule and rebuild — confirm the hash changes.

📄 View solution

Chapter 5 Quick Reference

  • app/globals.css imported in the root layout — applies the dark theme site-wide
  • CSS Modules (*.module.css) — Next.js's own built-in scoped-styling mechanism; class names are hashed automatically, no collisions possible
  • Automatic, verified cache-bustingnext build content-hashes every CSS output file with zero configuration
  • No new material — both mechanisms are Next.js's own built-in defaults, applied directly
  • Next chapter: Database with Prisma