TypeScript Strict Rules
Strict mode, no any, narrow types, exhaustive checks, branded types.
# CLAUDE.md — TypeScript Strict Rules
## tsconfig
- `strict: true` is the floor. Add the rest of the strictness flags:
```json
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitOverride": true,
"noFallthroughCasesInSwitch": true,
"useUnknownInCatchVariables": true,
"verbatimModuleSyntax": true
}
}
```
- ESLint `@typescript-eslint/no-explicit-any` is `error`, not `warn`.
- `noEmitOnError: true` so a compile error fails the build.
## Anti-`any`
- `any` is banned. Use `unknown` and narrow.
- Stuck on a third-party type? Write a `.d.ts` declaration that's tighter than the upstream.
- `// @ts-expect-error` over `// @ts-ignore` — it errors when the issue is fixed, telling you to remove it.
## Type narrowing
- Use type predicates (`x is Foo`) for custom narrows. They're functions returning a typed boolean.
- Discriminated unions with a literal `kind` / `type` tag for state machines and result types:
```ts
type Result<T> = { ok: true; value: T } | { ok: false; error: string }
```
- `switch` on the discriminator with `default: assertNever(x)` for exhaustiveness.
## `as` casts
- `as` is a sharp tool. Allow only:
- Narrowing `unknown` after a runtime check
- `const` assertions: `as const`
- Casting an empty array to a specific tuple type
- Forbidden: `as Foo`, `as unknown as Bar`. If you need it, you're missing a runtime check or a type predicate.
## Branded types
- Use branded types for IDs and other primitives that look the same but aren't:
```ts
type UserId = string & { readonly __brand: "UserId" }
```
- Construct via a factory function that runs the validation. Don't cast.
## Generics
- Single-letter generics for simple cases (`T`, `K`, `V`). Descriptive names for complex ones (`TUser`, `TKey`).
- Constrain generics with `extends` — unbounded `T` is rarely what you want.
- Avoid generics that exist only to "remember" a type. If the type is the same in and out, you don't need a generic.
## Errors
- Throw `Error` subclasses with a `name`. Don't throw strings.
- `try/catch` with `useUnknownInCatchVariables` forces you to narrow before reading the error. Use `if (err instanceof MyError)`.
- Don't return errors via union types from unrelated functions. Pick a layer where Result types make sense and stay there.
## Don't
- Don't use `Object`, `Function`, `{}` as types. They mean "anything".
- Don't write `Array<X>` and `X[]` mixed in the same file. Pick one.
- Don't use TypeScript enums. Use string-literal unions or `as const` objects.
- Don't write barrel files that re-export everything — they break tree-shaking.
Other TypeScript templates
Next.js App Router + TypeScript Rules
Server Components, Server Actions, and TypeScript discipline for the Next.js App Router.
Next.js + Server Actions + Shadcn UI
Forms with Server Actions, Zod validation, and Shadcn UI primitives.
Next.js + Supabase Auth
Auth flows with Supabase SSR, cookies, and Row Level Security policies.
Next.js + Tailwind + Prisma Stack
Full-stack Next.js with Prisma ORM, Tailwind CSS, and Postgres.
Next.js SEO + Metadata Rules
Metadata API, sitemap, robots, OG images, and structured data done right.