When we shipped the cohort dashboard in 2023, REST felt obvious. Two years later, it had become a liability — duplicated types between client and server, runtime crashes after schema changes, and a backlog of integration bugs that nobody wanted to own. We migrated to tRPC over a single quarter. This is the honest write-up.
The pain we were trying to fix
Every endpoint had three sources of truth: the route handler, the OpenAPI doc, and the React Query hook. They drifted constantly. Field renames silently broke the client. Adding a column to a payload required four PRs across two repos. None of it was anyone's fault — it was just the cost of REST without strong tooling, and we kept paying it.
Why tRPC, not GraphQL
GraphQL was on the table. We rejected it for two reasons: we don't need cross-team schema federation (we own client and server), and the operational tax of caching, persisted queries, and N+1 monitoring was bigger than the type-safety it bought us. tRPC gave us end-to-end types in TypeScript with zero codegen — perfect for a single-stack product team.
The migration that didn't break login
We ran tRPC alongside REST on every route, behind a feature flag per endpoint. Clients consumed both. We migrated read-heavy paths first, then writes. The only place we couldn't move was the auth handshake — Better Auth has its own contract, so it stayed REST. Six weeks in we cut the flag globally. Login uptime was 99.99% across the rollout.
The cold-start gotcha
tRPC's type inference adds compile time. On Vercel, a fresh build picked up an extra 18s of TypeScript work. We fixed it by lifting the router into a workspace package and pre-building it via Turbo cache. Without that, dev experience suffers and CI starts to drag.
What we'd do differently
Move tests in lockstep with the migration, not after. We accumulated a tail of integration tests that asserted REST contracts and had to be rewritten anyway — splitting it across two PR cycles instead of one would have saved a week.