Every technology in the default stack, why it's here, and how to use it.
Full-stack React framework with file-based routing, server components, and built-in API routes.
Why we use it
Single framework for both frontend and backend. Server components reduce client bundle size. API routes live alongside pages.
// app/api/hello/route.ts
export async function GET() {
return Response.json({ message: 'Hello world' })
}
// app/dashboard/page.tsx
export default async function Dashboard() {
const data = await fetch('/api/data') // server-side
return <main>{/* ... */}</main>
}Utility-first CSS with a full design system. Components are built on Base UI primitives for accessibility.
Why we use it
CSS variables for theming (dark/light mode included). No runtime overhead. shadcn/ui components are copy-owned — no lock-in.
// components/ui/button.tsx is copy-owned — edit freely
import { Button } from '@/components/ui/button'
<Button variant="outline" size="sm">Click me</Button>
<Button variant="destructive">Delete</Button>
// Tailwind utilities work everywhere
<div className="bg-card border border-border rounded-lg p-4">
<p className="text-muted-foreground text-sm">Card content</p>
</div>TypeScript-first schema declaration and validation. Used at every API boundary and for external data.
Why we use it
Validates and types data at the same time. One schema → TypeScript type + runtime validation. Required by AGENTS.md security rules.
import { z } from 'zod'
const CreateUserSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
role: z.enum(['admin', 'editor', 'viewer']),
})
// In an API route:
export async function POST(req: Request) {
const body = await req.json()
const parsed = CreateUserSchema.safeParse(body)
if (!parsed.success) return Response.json({ error: parsed.error }, { status: 400 })
// parsed.data is fully typed ✓
}ESLint enforces code quality and Next.js best practices. Prettier ensures consistent formatting.
Why we use it
Catches common mistakes early. Format-on-save in Cursor/VS Code. Tailwind class order is auto-sorted by prettier-plugin-tailwindcss.
# Run both before every commit:
npm run check # tsc --noEmit && eslint
# Or run separately:
npm run typecheck # TypeScript only
npm run lint # ESLint only
npm run format # Prettier (auto-fix)Fast unit test runner with native TypeScript support and Jest-compatible API.
Why we use it
Uses the same Vite config as the project — no separate bundler setup. Instant hot reloading in watch mode. Jest drop-in replacement.
// app/__tests__/health.test.ts
import { expect, test } from 'vitest'
test('health endpoint returns ok', async () => {
const res = await fetch('http://localhost:3000/api/health')
const json = await res.json()
expect(json.status).toBe('ok')
})
# Run tests:
npm run test # run once
npx vitest --watch # watch mode