Project Setup
Establish a clean, consistent project foundation — repository structure, tooling, TypeScript configuration, and environment management.
A well-structured project foundation prevents entire categories of problems. Decisions made here — folder layout, TypeScript strictness, linting rules — become the ground truth that every developer on the team works from. Get these right at the start.
Repository Structure
Structure code by feature proximity, not by technical layer. Grouping all hooks/ together and all components/ together at the root level creates unnecessary distance between related code. Instead, keep feature-related files close to each other.
src/
app/ # Next.js App Router pages and layouts
(auth)/ # Route group — no URL segment
dashboard/
page.tsx
layout.tsx
api/
components/
ui/ # Primitive components (Button, Input, etc.)
layout/ # Header, Sidebar, Footer
[feature]/ # Feature-specific composed components
lib/
utils.ts # Pure utility functions
constants.ts # App-wide constants
validations.ts # Zod schemas
hooks/ # Shared custom hooks
types/ # Global TypeScript types
styles/ # Global CSS, design tokens
public/ # Static assets (images, fonts, icons)The components/ui/ directory should contain only unstyled or lightly-styled primitive components. Composed feature components belong in components/[feature]/. This distinction matters when refactoring.
Tooling Choices
| Concern | Recommended Tool | Rationale |
|---|---|---|
| Package manager | pnpm | Fastest installs, strict dependency resolution, disk-efficient |
| Bundler (Next.js) | Turbopack (default in Next.js 16) | Significantly faster dev builds than Webpack |
| TypeScript | 5.x strict mode | Catches the bugs that matter; strict null checks prevent entire categories of runtime errors |
| CSS | Tailwind CSS v4 | Utility-first, no dead CSS in production, design token integration |
| Linting | ESLint + eslint-config-next | Catches accessibility, hooks, and import issues automatically |
| Formatting | Prettier | Removes formatting debates from code review permanently |
| Git hooks | Husky + lint-staged | Enforces lint/format on commit, not in CI |
TypeScript Configuration
Always enable strict mode. The short-term pain of stricter types pays off within days as the compiler catches problems that would otherwise reach production.
{
"compilerOptions": {
"target": "ES2022",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": false,
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"exactOptionalPropertyTypes": true,
"moduleResolution": "bundler",
"jsx": "preserve",
"incremental": true,
"paths": {
"@/*": ["./src/*"]
}
}
}Path Aliases
Configure @/ as the root alias. Relative imports like ../../../components/Button are fragile and hurt readability. @/components/ui/button is unambiguous regardless of where the importing file lives.
Environment Management
Never commit secrets. Never hardcode environment-specific values. Use a structured environment file pattern with validation.
import { z } from "zod"
const envSchema = z.object({
// Server-only
DATABASE_URL: z.string().url(),
SESSION_SECRET: z.string().min(32),
// Public (exposed to browser)
NEXT_PUBLIC_APP_URL: z.string().url(),
NEXT_PUBLIC_ENV: z.enum(["development", "staging", "production"]),
})
export const env = envSchema.parse(process.env)
// Throws at startup if any required variable is missing or malformed.| File | Purpose | Committed? |
|---|---|---|
.env.example | Documents all required variables with placeholder values | Yes |
.env.local | Local developer overrides | No (.gitignore) |
.env.staging | Staging environment values | No — inject via CI |
.env.production | Production values | No — inject via CI |
Linting & Formatting
{
"extends": [
"next/core-web-vitals",
"next/typescript"
],
"rules": {
"no-console": ["warn", { "allow": ["warn", "error"] }],
"prefer-const": "error",
"no-unused-vars": "error",
"@typescript-eslint/no-explicit-any": "error",
"jsx-a11y/alt-text": "error",
"jsx-a11y/anchor-is-valid": "error"
}
}{
"semi": false,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100,
"plugins": ["prettier-plugin-tailwindcss"]
}The prettier-plugin-tailwindcss plugin automatically sorts Tailwind class names into a consistent order. This eliminates an entire category of unnecessary merge conflicts.
Git Conventions
Consistent commit messages make release notes, code archaeology, and automated changelogs possible. Adopt Conventional Commits from day one.
For the full workflow covering branch strategy, PR slicing, redesign coordination, and agent-safe collaboration in team repositories, see the Collaborative Repository Workflow page.
| Prefix | Use for |
|---|---|
feat: | New user-facing feature |
fix: | Bug fix |
refactor: | Code change with no behavior change |
perf: | Performance improvement |
test: | Adding or updating tests |
docs: | Documentation only |
chore: | Tooling, dependencies, config |
ci: | CI/CD pipeline changes |
Stakeholder Communication
Present design decisions, give and receive critique, and translate between designers, developers, and clients — the soft skills that make design engineering work.
Collaborative Repository Workflow
Work safely with designers, developers, and agents in the same repository while redesigning UI, shipping features, and keeping the app functional.