Next.js App Router Scaffold
When to use
- Starting a new web project from scratch
- User asks to "create a Next.js app" or "scaffold a project"
- Need a custom server (WebSocket, Socket.io, SSE alongside Next.js)
Stack decisions
| Layer | Default choice | Why |
|---|---|---|
| Framework | Next.js (App Router) | Server components, file-based routing, streaming |
| Language | TypeScript (strict) | Catch errors at build time |
| Styling | Tailwind CSS v4 | @theme design tokens, no runtime CSS-in-JS |
| Runtime | Node.js 22 LTS | Long-term support, native fetch |
Project init
npx create-next-app@latest PROJECT_NAME \
--typescript --tailwind --eslint --app \
--src-dir --import-alias "@/*"
cd PROJECT_NAME
Tailwind v4 uses @import "tailwindcss" in CSS — no tailwind.config.js needed. Design tokens go in @theme {} block inside globals.css.
Directory structure
PROJECT_NAME/
├── src/
│ ├── app/ # Routes (App Router)
│ │ ├── layout.tsx # Root layout (fonts, metadata, providers)
│ │ ├── page.tsx # Home page
│ │ └── globals.css # @theme tokens + global styles
│ ├── components/
│ │ ├── layout/ # Shell, nav, footer, overlays
│ │ ├── shared/ # Reusable UI (cards, modals, inputs)
│ │ └── [feature]/ # Feature-specific components
│ ├── hooks/ # Custom React hooks
│ └── lib/ # Utilities, types, data, API clients
├── public/ # Static assets
├── server.mjs # Custom server (if needed)
├── Dockerfile # Multi-stage build
├── package.json
├── tsconfig.json
└── next.config.ts
Custom server pattern (server.mjs)
Use when you need to run additional services (Socket.io, cron, etc.) alongside Next.js on a single port. This is the escape hatch — only use it when Next.js API routes are insufficient.
import { createServer } from "node:http";
import next from "next";
const dev = process.env.NODE_ENV !== "production";
const hostname = "0.0.0.0";
const port = parseInt(process.env.PORT || "3000", 10);
const app = next({ dev, hostname, port });
const handler = app.getRequestHandler();
await app.prepare();
const httpServer = createServer(handler);
// Attach additional services to httpServer here
// e.g. new Server(httpServer, { cors: { origin: "*" } })
httpServer.listen(port, hostname, () => {
console.log(`> Ready on http://${hostname}:${port}`);
});
Update package.json scripts:
{
"scripts": {
"dev": "node server.mjs",
"build": "next build",
"start": "node server.mjs"
}
}
Root layout pattern
import type { Metadata } from "next";
import { Inter, Space_Grotesk } from "next/font/google";
import "./globals.css";
const inter = Inter({ subsets: ["latin"], variable: "--font-inter" });
const display = Space_Grotesk({ subsets: ["latin"], variable: "--font-space-grotesk" });
export const metadata: Metadata = {
title: "Your App",
description: "Description",
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className={`${inter.variable} ${display.variable}`}>
<body className="font-sans antialiased">{children}</body>
</html>
);
}
Tailwind v4 globals.css skeleton
@import "tailwindcss";
@theme {
--color-primary: #your-color;
--color-accent: #your-accent;
--font-sans: var(--font-inter, "Inter"), ui-sans-serif, system-ui, sans-serif;
--font-display: var(--font-space-grotesk, "Space Grotesk"), ui-sans-serif, system-ui, sans-serif;
}
Content-data separation
Keep all content (text, config, questions, scenarios) in src/lib/ data files, separate from components. Components import data — never hardcode content strings in TSX.
// src/lib/types.ts — shared interfaces
export interface PageInfo {
title: string;
description: string;
slug: string;
}
// src/lib/page-data.ts — content
import type { PageInfo } from "./types";
export const pages: PageInfo[] = [ /* ... */ ];
Key dependencies to install
Only add what you need:
# Animation (if interactive UI)
npm install framer-motion
# Real-time (if multi-user)
npm install socket.io socket.io-client
# Email (if transactional email)
npm install resend
# Drag & drop (if sortable UI)
npm install @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities
Checklist
-
src/app/layout.tsxhas metadata, fonts, global CSS import -
globals.cssuses@theme {}for design tokens (no tailwind.config.js) - Types in
src/lib/types.ts, content insrc/lib/*-data.ts - Components organized by role:
layout/,shared/,[feature]/ - If custom server:
server.mjsat root, scripts updated -
.gitignoreincludes.next/,node_modules/,.env*.local