# TypeScript Next.js Rules

> **Context skill:** Install this into your agent to enforce consistent, production-quality patterns in TypeScript Next.js App Router projects. Works with Claude Code, Cursor, Windsurf, and any agent that reads context files.

---

## Stack Assumptions

- **Next.js 14+** with App Router (not Pages Router)
- **TypeScript** with strict mode enabled (`"strict": true` in tsconfig)
- **Tailwind CSS** for styling
- **Zod** for runtime validation
- **Supabase or Prisma** for database (adapt as needed)

---

## File & Directory Conventions

```
app/
  (group)/          ← Route groups (no URL segment)
    layout.tsx      ← Shared layout
    page.tsx        ← Route page (server component by default)
    loading.tsx     ← Suspense fallback
    error.tsx       ← Error boundary ('use client' required)
  api/
    route.ts        ← API routes (GET, POST, etc. as named exports)
components/
  ui/               ← Primitive UI: Button, Card, Badge, etc.
  feature/          ← Feature-specific composed components
lib/
  utils/            ← Pure utility functions (no React)
  types/            ← TypeScript type definitions
  actions/          ← Server Actions
hooks/              ← Custom React hooks (client-side)
```

---

## Server vs Client Components

**Default to Server Components.** Only add `'use client'` when you need:
- `useState` or `useReducer`
- `useEffect` or lifecycle hooks
- Browser APIs (`window`, `localStorage`, etc.)
- Event handlers that run in the browser
- Third-party client-only libraries

**Never** put `'use client'` at the top of a layout file.

---

## TypeScript Rules

```typescript
// ✅ Always type function return values explicitly
async function getUser(id: string): Promise<User | null> { ... }

// ✅ Use `type` for unions/intersections, `interface` for object shapes
type Status = 'pending' | 'active' | 'archived'
interface UserProfile { id: string; email: string; }

// ✅ Never use `any` — use `unknown` and narrow
function parse(input: unknown): string {
  if (typeof input !== 'string') throw new Error('Expected string')
  return input
}

// ❌ Never
const data: any = response.json()

// ✅ Type API responses with Zod
const UserSchema = z.object({ id: z.string().uuid(), email: z.string().email() })
type User = z.infer<typeof UserSchema>
```

---

## API Routes

```typescript
// app/api/users/route.ts
import { NextResponse } from 'next/server'
import { z } from 'zod'

const CreateUserSchema = z.object({
  email: z.string().email(),
  name: z.string().min(1).max(100),
})

export async function POST(request: Request) {
  const body = await request.json()
  const result = CreateUserSchema.safeParse(body)
  if (!result.success) {
    return NextResponse.json({ error: result.error.flatten() }, { status: 422 })
  }
  // ... proceed with result.data (fully typed)
}
```

---

## Data Fetching

```typescript
// ✅ Server Component data fetching — no useEffect
export default async function UsersPage() {
  const users = await getUsers() // Direct async call in component
  return <UserList users={users} />
}

// ✅ Parallel fetching with Promise.all
const [user, posts] = await Promise.all([getUser(id), getPosts(id)])

// ✅ Use `cache` for request deduplication
import { cache } from 'react'
const getUser = cache(async (id: string) => { ... })
```

---

## Error Handling

```typescript
// ✅ Never throw in Server Components — use error.tsx
// app/(main)/error.tsx
'use client'
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
  return <div><h2>Something went wrong</h2><button onClick={reset}>Try again</button></div>
}

// ✅ API routes always return typed error shapes
return NextResponse.json({ error: 'Not found', code: 'USER_NOT_FOUND' }, { status: 404 })
```

---

## Tailwind CSS

- Use Tailwind utility classes directly — no custom CSS unless absolutely necessary
- Use `cn()` helper (from `clsx` + `tailwind-merge`) to conditionally join classes
- Dark mode via `dark:` variants (not separate stylesheets)
- Never use inline `style={{}}` for values that Tailwind covers
- Use `@apply` sparingly — only in component-level `.module.css` files if needed

---

## Do Not

- Do not use `pages/` directory — App Router only
- Do not import server-only code into client components (will cause runtime errors)
- Do not use `getServerSideProps` or `getStaticProps` — use async Server Components
- Do not `console.log` in production code — use a proper logger
- Do not hardcode environment variables — use `process.env.VAR` with validation on startup
- Do not use default exports for utility functions — named exports only
