T3 Stack Gotcha: Stale Prisma Client After Migration
API endpoints returned 500 errors with 'Unknown field legacy_status' from stale Prisma client.
- The T3 Stack is a full-stack TypeScript template combining Next.js, tRPC, Prisma, Tailwind CSS, and TypeScript for end-to-end type safety
- Next.js 15/16 App Router with Server Components eliminates API route boilerplate — data fetching happens directly in server-rendered components
- tRPC v11 provides end-to-end type inference from database schema through API layer to frontend components with zero code generation
- Prisma generates a type-safe client from your schema.prisma — every query is checked at compile time against your actual database schema
- shadcn/ui replaces traditional component libraries — you copy components into your project and own the code, no version-lock dependency
- The stack's power is the type chain: Prisma schema infers Prisma types, tRPC infers API types, React infers prop types — one change propagates everywhere
The T3 Stack is not a framework — it's a curated set of tools that together provide end-to-end type safety from database to browser. Each tool solves a specific problem: Next.js for rendering and routing, tRPC for type-safe APIs, Prisma for type-safe database access, Tailwind for styling, and TypeScript as the connective tissue.
In 2026, T3 has split into two main patterns: (1) RSC-first: Server Components read directly from Prisma with Server Actions for writes (zero network hop), and (2) tRPC-everywhere: classic T3 with tRPC for both reads and writes. This guide covers pattern #2 because it is still the default create-t3-app setup, but we’ll clearly call out where the RSC-first pattern wins.
The 2026 iteration has evolved significantly. Next.js 15/16’s App Router with Server Components changes where data fetching happens. tRPC v11 integrates natively with TanStack Query for caching and optimistic updates. Prisma’s edge runtime makes serverless deployments viable. shadcn/ui replaces bloated component libraries with copy-paste components you fully own.
This guide covers the architecture decisions, the type chain that makes the stack powerful, the production pitfalls that catch teams off guard, and the migration path from earlier versions.
The Type Chain: How the T3 Stack Propagates Types End-to-End
The T3 Stack's core value proposition is the type chain — a single change to your database schema propagates type safety through every layer of your application without manual synchronization.
It starts with Prisma's schema.prisma. When you define a model, Prisma generates a TypeScript client where every query is typed against your schema. The output of a Prisma query is a typed object that flows into your tRPC procedure. tRPC infers the procedure's output type from the return value. On the client, tRPC's useQuery hook infers the response type from the procedure definition. React components receive typed props from the tRPC response.
The chain: schema.prisma → Prisma Client types → tRPC procedure types → React component props. One change at the top propagates to the bottom. If you rename a column, TypeScript catches every reference that's now broken — in your API layer, your frontend components, and your form validations.
tRPC v11: Type-Safe APIs Without Code Generation
tRPC v11 eliminates the manual synchronization between backend API definitions and frontend type expectations. You define a procedure (query or mutation) on the server with a zod input schema and a typed resolver. The client imports the router type and gets full autocompletion, compile-time type checking, and zero code generation.
The v11 improvements over v10 include native TanStack Query v5 integration, better error handling with typed TRPCError responses, and improved middleware composition. The httpBatchLink now supports streaming — multiple procedures in a single HTTP request with progressive response delivery.
The key architectural decision is router composition. You define separate routers for each domain (users, posts, comments) and merge them into a root appRouter. The client sees a flat API surface: trpc.users.getById.useQuery() — the router hierarchy is invisible on the client side.
Prisma: Type-Safe Database Access in the Edge Runtime Era
Prisma generates a TypeScript client from your schema.prisma file. Every query — findMany, create, update, delete — is typed against your actual database schema. If you rename a column, TypeScript catches every query that references the old name at compile time.
The 2026 evolution is Prisma's edge runtime support. Traditional Prisma requires a Node.js binary for database connections — incompatible with edge runtimes (Cloudflare Workers, Vercel Edge Functions). Prisma's driver adapters (for Neon, PlanetScale, Turso) enable edge-compatible database access by using platform-native drivers instead of Prisma's binary engine.
The architectural decision is connection pooling. In serverless/edge environments, each function invocation opens a new database connection. Without pooling, you exhaust the database's connection limit at ~100 concurrent requests. Prisma Accelerate or a database-native pooler (PgBouncer, Neon's built-in pooler) is mandatory for production serverless deployments.
shadcn/ui: Owning Your Component Code Instead of Depending on It
shadcn/ui is not a component library — it's a collection of copy-paste components built on Radix UI primitives and styled with Tailwind CSS. You run npx shadcn-ui@latest add button and the component source code is copied into your project. You own it. You modify it. There's no version-locked dependency to update.
This solves a real production problem: traditional component libraries (MUI, Ant Design, Chakra) ship as npm packages. When you need to customize a component beyond the library's API surface, you fight the abstraction. shadcn/ui gives you the source code — customization is just editing a file in your project.
The architecture is built on three layers: Radix UI provides accessible primitives (keyboard navigation, ARIA attributes, focus management), Tailwind CSS provides the styling system (utility classes, CSS variables for theming), and shadcn/ui provides the composition layer that combines them into production-ready components.
Project Structure: Organizing the T3 Stack for Scale
The T3 Stack's project structure determines how maintainable the codebase is at scale. A flat structure works for prototypes but collapses at 50+ procedures and 100+ components. The recommended structure separates concerns by domain, not by technology layer.
The key organizational principle is domain-driven: each feature (users, posts, comments) gets its own directory containing its Prisma model, tRPC router, Server Components, Client Components, and shared types. This keeps related code together — when you modify the posts feature, everything you need is in one directory.
The alternative — organizing by technology layer (all routers in one directory, all components in another) — forces you to jump between 5 directories to understand a single feature. At scale, this cognitive overhead slows development to a crawl.
| Component | Problem It Solves | What It Replaces | 2026 Evolution |
|---|---|---|---|
| Next.js 15/16 | Rendering, routing, data fetching | Express + React SPA, manual SSR setup | App Router, Server Components, Server Actions, PPR |
| tRPC v11 | Type-safe API layer without code generation | REST/GraphQL with manual type definitions | Native TanStack Query v5, streaming, better error types |
| Prisma | Type-safe database queries from schema | Raw SQL, Sequelize, TypeORM | Edge runtime driver adapters, Prisma Accelerate |
| Tailwind CSS | Utility-first styling with design tokens | CSS modules, styled-components, Sass | Oxide engine (10x faster builds), container queries |
| shadcn/ui | Owned component code with accessible primitives | MUI, Ant Design, Chakra (dependency-locked) | New York style, improved theming, date picker |
| TypeScript | End-to-end type safety across the stack | JavaScript (no type checking) | Satisfies operator, const type parameters, isolated declarations |
Key Takeaways
- The T3 Stack's core value is the type chain: schema.prisma → Prisma Client → tRPC → React — one change propagates everywhere.
- tRPC v11 eliminates manual API type synchronization — define a procedure once, the client gets autocompletion and caching for free.
- shadcn/ui gives you component source code you own — no version-lock dependency, no abstraction fights.
- Connection pooling is mandatory for serverless Prisma — without it, you exhaust database connections at ~100 concurrent users.
- Organize by domain, not by layer — one directory per feature keeps related code together at scale.
Common Mistakes to Avoid
- Not regenerating Prisma client after schema changes in CI
Symptom: TypeScript compiles successfully but runtime queries fail with 'Unknown field' errors. The deployed Prisma client is stale — its types don't match the actual database schema after a migration.
Fix: Reorder CI pipeline: (1) prisma migrate deploy, (2) prisma generate, (3) next build. Add prisma generate to postinstall scripts. Run prisma validate before build to catch schema drift. - Placing 'use client' at the top of the T3 Stack app router (e.g., dashboard/layout.tsx)
Symptom: Every component becomes a Client Component — Server Components are completely disabled. Bundle size is identical to a traditional React SPA. No RSC performance benefits.
Fix: Remove 'use client' from layout.tsx. Only mark leaf components that need useState, useEffect, or event handlers. Keep Server Components as the default. - Not invalidating tRPC query cache after mutations
Symptom: User creates a post, sees success message, but the post list still shows old data. The mutation succeeded but TanStack Query's cache wasn't invalidated.
Fix: Call trpc.useUtils().post.list.invalidate() after the create mutation completes. Use onSettled in useMutation options to invalidate regardless of success or failure. - Organizing the project by technology layer instead of domain
Symptom: Adding a feature requires touching 5+ directories (routers/, components/, types/, validations/, hooks/). Developers spend more time navigating than coding. Feature ownership is unclear.
Fix: Restructure to domain-driven: each feature gets its own directory with its router, components, and types. Shared code goes in lib/ and components/ui/. - Using Prisma without connection pooling in serverless
Symptom: Database connection errors at ~100 concurrent users. Each serverless function opens a new connection — the database's max_connections limit (default 100 for PostgreSQL) is exhausted.
Fix: Use Prisma Accelerate or a database-native pooler (PgBouncer, Neon's built-in pooler). Connection pooling reuses connections across function invocations.
Interview Questions on This Topic
- QExplain the type chain in the T3 Stack. How does a schema change propagate type safety through the entire application?Mid-levelReveal
- QHow does tRPC differ from REST and GraphQL? When would you choose one over the others?Mid-levelReveal
- QWhat is the difference between shadcn/ui and a traditional component library like MUI? Why would you choose one over the other?JuniorReveal
- QHow does Prisma's edge runtime support work, and why is connection pooling mandatory in serverless?SeniorReveal
- QWalk me through the recommended CI/CD pipeline for a T3 Stack project. What order should build steps run in, and why?SeniorReveal
Frequently Asked Questions
Can I use the T3 Stack without tRPC?
Yes. The T3 Stack is modular — you can replace tRPC with REST API routes, GraphQL, or Server Actions. The type chain still works through Prisma, but you lose end-to-end inference on the API layer. You'd need to manually define request/response types or use OpenAPI codegen. tRPC is the biggest type-safety multiplier in the stack — removing it is possible but leaves a gap in the chain.
How does shadcn/ui handle component updates?
shadcn/ui components are copied into your project — they don't update automatically like npm packages. Run npx shadcn-ui@latest diff to see what changed upstream. Then manually merge the changes into your customized components. This is intentional: you own the code, so you control when and how updates are applied. No breaking changes surprise you in a minor version bump.
What database should I use with the T3 Stack in 2026?
PostgreSQL is the default and recommended choice — Prisma has the deepest support, and edge-compatible options (Neon, Supabase) are mature. MySQL is fully supported. SQLite works for local development and small apps. For edge deployments, use Neon (Postgres) or Turso (SQLite) with Prisma's driver adapters. Avoid databases without Prisma support unless you're willing to write raw SQL.
Can I use the T3 Stack with a monorepo?
Yes — the T3 Stack works well in Turborepo or Nx monorepos. The recommended pattern: put the Prisma schema and generated client in a shared package, import it into the Next.js app and any other services. Share zod validation schemas the same way. This prevents schema drift between services that share the same database.
That's React.js. Mark it forged?
4 min read · try the examples if you haven't