Design Engineering
Foundations

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.

Recommended directory layout
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

ConcernRecommended ToolRationale
Package managerpnpmFastest installs, strict dependency resolution, disk-efficient
Bundler (Next.js)Turbopack (default in Next.js 16)Significantly faster dev builds than Webpack
TypeScript5.x strict modeCatches the bugs that matter; strict null checks prevent entire categories of runtime errors
CSSTailwind CSS v4Utility-first, no dead CSS in production, design token integration
LintingESLint + eslint-config-nextCatches accessibility, hooks, and import issues automatically
FormattingPrettierRemoves formatting debates from code review permanently
Git hooksHusky + lint-stagedEnforces 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.

tsconfig.json — key options
{
  "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.

env validation with Zod — lib/env.ts
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.
FilePurposeCommitted?
.env.exampleDocuments all required variables with placeholder valuesYes
.env.localLocal developer overridesNo (.gitignore)
.env.stagingStaging environment valuesNo — inject via CI
.env.productionProduction valuesNo — inject via CI

Linting & Formatting

.eslintrc.json
{
  "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"
  }
}
.prettierrc
{
  "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.

PrefixUse 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

On this page