Opinionated, production-ready Rules for building fully type-safe applications with the T3 Stack (Next.js 15+, TypeScript, tRPC, Prisma, Tailwind CSS, NextAuth.js).
You're tired of runtime errors that could have been caught at compile time. You're switching between different mental models for your database, API, and frontend. You're manually syncing types across your stack and praying nothing breaks in production.
The T3 Stack promises end-to-end type safety, but without proper patterns, you end up with:
These Cursor Rules transform your T3 Stack development into a compiler-enforced, end-to-end type-safe workflow. Instead of juggling separate mental models for Next.js, tRPC, and Prisma, you get a unified development experience where the TypeScript compiler proves your application's correctness from database to UI.
What makes this different: These rules enforce architectural patterns that make impossible states actually impossible. Your database schema drives your API types, which drive your frontend types, with zero manual synchronization.
// Before: Manual type declarations that can drift
interface User {
id: string;
email: string; // Oops, forgot this is optional in the DB
}
// After: Single source of truth
export const userSchema = z.object({
id: z.string().cuid(),
email: z.string().email().optional()
});
export type User = z.infer<typeof userSchema>;
Server components by default + selective client hydration means your JavaScript bundles stay lean. The rules enforce this pattern automatically.
// Built-in pattern for safe tRPC procedures
export const protectedProcedure = t.procedure
.use(isAuthed) // Compile-time auth requirement
.input(z.object({ id: z.string().cuid() }))
.mutation(async ({ ctx, input }) => {
// Type-safe all the way down
});
Pre-configured TypeScript strict mode, automated migrations, lint-on-save, and CI/CD patterns that just work.
prisma generate - types automatically propagateapi.post.all.useQuery() has full IntelliSenseNo manual type synchronization. No runtime surprises.
// Session data is typed and minimal by default
const session = await getServerSession();
// TypeScript knows: session.user.id, session.user.email, session.user.role
The rules enforce minimal session exposure and proper edge runtime usage.
// Server component by default
export default async function PostsPage() {
const posts = await api.post.all.query(); // Direct server call
return <PostList posts={posts} />;
}
// Client component only when needed
'use client';
export function InteractivePost() {
const mutation = api.post.create.useMutation();
// Client-side interaction logic
}
No more accidental client-side data fetching. No more shipping server logic to browsers.
# Install with these exact versions for compatibility
npx create-t3-app@latest --prisma --nextauth --tailwind --trpc
Copy the provided configuration into your .cursorrules file. The rules automatically enforce:
// package.json scripts the rules expect
{
"scripts": {
"dev": "next dev --turbo",
"db:generate": "prisma generate",
"db:migrate": "prisma migrate dev",
"type-check": "tsc --noEmit",
"lint": "eslint . --max-warnings 0"
}
}
The rules enforce a clear separation:
app/ - Next.js routes and server componentsserver/ - tRPC routers and database logiccomponents/ - Reusable UI componentsstyles/ - Tailwind component styles// This pattern is enforced everywhere
try {
return await ctx.prisma.post.delete({ where: { id: input.id } });
} catch (err) {
ctx.logger.error(err, 'Failed to delete post');
throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', cause: err });
}
Every mutation has proper error handling. Every API route has input validation. Every component has predictable data flow.
Ready to eliminate runtime type errors and build with confidence? These T3 Stack rules transform your development workflow from hoping-it-works to proving-it-works at compile time.
Your future self (and your team) will thank you when that production deployment just works.
You are an expert in the T3 Stack (TypeScript 5.x, Next.js 15+, tRPC ^10, Prisma ^5, Tailwind CSS ^3, NextAuth.js ^5).
Key Principles
- End-to-end type safety: the compiler must prove correctness from database to UI.
- Prefer server components/actions; ship zero client JS unless interaction is required.
- Keep functions pure and small; colocate mutations with the data they mutate.
- Fail fast: validate input and throw typed errors early, return the happy path last.
- Enforce a clear, flat folder structure: `app`, `server`, `components`, `styles`, `tests`.
- Automate everything: migrations, lint, test, type-check, deploy via CI.
TypeScript
- Use `strict`, `noImplicitOverride`, `exactOptionalPropertyTypes`, `verbatimModuleSyntax`.
- Derive types from sources, never re-declare. Example:
```ts
// GOOD – infer from zod schema
export const userSchema = z.object({ id: z.string().cuid(), email: z.string().email() });
export type User = z.infer<typeof userSchema>;
```
- Naming
• Interfaces prefix: `IUser`. • PascalCase for types, interfaces, components. • camelCase for vars/funcs. • `_private` prefix for private helpers. • Snake_case never.
- Use generics for reusable utils, e.g. `paginate<T>()`.
- Prefer `unknown` in `catch` clauses; narrow with `instanceof` or predicates.
- Export a single default per file only for React components; otherwise use named exports.
Error Handling & Validation
- Wrap async server logic in `try/catch`, re-throw `TRPCError` with `code` and `message`.
- Centralise logging in `/server/utils/logger.ts`; use `pino` with `browser` transport for client.
- Example safe procedure wrapper:
```ts
export const protectedProcedure = t.procedure
.use(isAuthed) // throws UNAUTHORIZED
.input(z.object({ id: z.string().cuid() }))
.mutation(async ({ ctx, input }) => {
try {
return await ctx.prisma.post.delete({ where: { id: input.id } });
} catch (err) {
ctx.logger.error(err, 'Failed to delete post');
throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', cause: err });
}
});
```
Next.js 15
- Use the `app/` router exclusively. Route groups: `(marketing)`, `(auth)` etc.
- Server Components by default. Opt-in to client with `'use client'` pragma.
- Server Actions: place mutations next to the component that triggers them, export `revalidatePath`.
- Hybrid rendering: default SSR, use `generateStaticParams` + `revalidate` for ISR where data is cacheable.
- File naming: `page.tsx`, `layout.tsx`, `loading.tsx`, `error.tsx`.
- Prefer the Turbopack dev server; enable in `next.config.js`.
Prisma
- Use PostgreSQL. Keep schema atomic migrations, one per PR.
- Enable `prisma format` + `prisma lint` pre-commit.
- Use `@@index()` & `@@unique()` for query patterns. Always add `updatedAt @updatedAt`.
- Generate `clientExtensions` to attach computed fields (e.g., `fullName`).
- Never expose Prisma models directly to the client; wrap via tRPC procedures.
tRPC
- Export router in `/server/api/root.ts`, procedures in feature folders (`/server/api/posts.ts`).
- Split routers logically: `authRouter`, `postRouter`. Merge in `rootRouter`.
- Use `createTRPCNext` with `ssr: true` but only opt-in pages that require it.
- Treat queries as immutable; mutations must invalidate queries via `opts.contextUtils.invalidate()`.
- Example query hook:
```ts
const { data: posts } = api.post.all.useQuery(undefined, { staleTime: 10 * 1000 });
```
NextAuth.js
- Use edge runtime adapter (`{ adapter: PrismaAdapter(prisma) }`).
- Expose only minimal session data: `sub`, `role`, `email`.
- Store secrets in `.env`, never in code. Rotate every 90 days.
Tailwind CSS
- Enable JIT (`mode: 'jit'`) and `content` glob for `app/**/*.{ts,tsx}`.
- Use semantic component folders: `styles/components/button.css` with `@apply`.
- Never override Tailwind base; extend via `tailwind.config.ts` `theme.extend`.
- Use `clsx` utility for conditional classes.
Testing
- Jest + React Testing Library for UI; Vitest for server utils.
- Directory: `__tests__/unit`, `e2e/` (Playwright).
- BDD naming: `given/when/then`.
- Minimum coverage gate: 90 % statements, 100 % critical paths (auth, payment).
Performance
- Automatic image optimisation via `<Image>`.
- `next/font` for local fonts to avoid CLS.
- Dynamic imports for heavy components: `import('react-markdown')`.
- Database: use `prisma.$transaction()` for n>1 writes.
Security
- Enable strict CSP in `next.config.js` with nonce for inline scripts.
- Use `helmet` in custom `middleware.ts` for headers.
- Sanitize user HTML with `sanitize-html`.
- Limit tRPC payload size to 100 KB.
Directory Template
```
├── app/
│ ├── (auth)/
│ ├── (dashboard)/
│ └── globals.css
├── server/
│ ├── api/
│ ├── prisma/
│ ├── utils/
│ └── tests/
├── components/
├── styles/
├── tests/
└── scripts/
```
CI / CD
- Steps: Install → Lint (`eslint --max-warnings 0`) → Type-check → Test → Build.
- Block merge on failing migrations (`prisma migrate deploy --dry-run`).
Common Pitfalls & Remedies
- Forgetting to revalidate after mutations → always call `revalidatePath('/')` or `invalidate()`.
- Overusing client components → profile bundle; move to server if no interactivity.
- Direct Prisma leakage → export DTOs with `zod` omit private fields.
Adopt these rules to achieve a highly maintainable, scalable, and fully type-safe T3 Stack codebase.