My Stack of Choice for 2024
After over a year of working with Next.js across various projects with different requirements, I've found myself consistently returning to a set of technologies that have proven to be highly effective. These tools not only complement each other well but also streamline development, making them my go-to stack for 2024.
So, What is the T3 Stack?
The "T3 Stack" is a web development stack created by Theo, focused on simplicity, modularity, and full-stack type safety. It consists of:
- TypeScript
- Tailwind CSS
- Next.js
- NextAuth.js
- Prisma
- tRPC
The stack is modular, and you can change or discard certain technologies depending on your use cases. For a React app with a backend and an auth system, this is the way to go.
The best way to get started is with the CLI:
create-t3-app
. It’s a simple CLI made to scaffold a starter project using the T3 stack.
Why T3?
I think many people end up with something similar to T3 is the Axioms of T3:
- Solve Problems: It's easy to fall into the trap of "adding everything"—we explicitly don't want to do that. Everything added to should solve a specific problem that exists within the core technologies included.
create-t3-app
- Bleed Responsibly: We love our bleeding-edge tech. The speed and fun that come from new advancements are exciting. But it's important to bleed responsibly, using riskier tech in less risky parts.
- Type Safety Isn't Optional: The stated goal of is to provide the quickest way to start a new full-stack, type-safe web application. We take type safety seriously because it improves productivity and helps us ship fewer bugs.
create-t3-app
Both the second and third principles are crucial for me when choosing a stack.
What About Alternatives?
Why choose this tech over others? Well, generally speaking, it depends on what you need and what you believe in. Here’s my take:
- TypeScript: Despite “Type Safety Isn't Optional” being the principle, I don’t think we need to debate TypeScript vs. JavaScript anymore.
- Tailwind CSS: As a long-time frontend developer who has tried every approach possible to make working with CSS less painful, I’ve concluded that the best way to manage CSS is to not write CSS at all.
- NextAuth.js: If you want a full-featured authentication system with built-in providers (Google, Facebook, GitHub…), JWT, JWE, email/password, magic links, and more—use NextAuth.js. If you’re in an early-stage MVP or don’t need all the features NextAuth provides, solutions like Clerk and Kinde are great ways to provide auth in your app, and it takes less than 5 minutes to set up.
- ORM (Prisma): Since “Type Safety Isn't Optional,” using an ORM is mandatory. Prisma is the most popular choice for ORMs, generating types for models and queries on the fly. However, Prisma’s lack of stability, with breaking changes in versions 2 and 3, is a major turnoff for some people. Additionally, using an ORM can make you oblivious to the actual SQL queries you’re writing, leaving less room for optimization. Recently, I found myself using Drizzle, which provides a sweet spot between writing raw SQL queries (which are easy to optimize but prone to mistakes and lack type safety) and the Prisma approach of not knowing what’s going on under the hood of your queries.
- tRPC: Looking at the headline on tRPC’s website, you can see why I choose it.
Yes, “Type Safety Isn't Optional.” If you’re using GraphQL, you might think it also provides type safety because of the agreed schema between the frontend and backend. This works well for a separated frontend and backend (which has an unavoidable version management issue), but not for a full-stack monorepo. GraphQL requires a code-generating process, which leads to longer type-checking times, less IDE support, and a larger client bundle size. Additionally, it introduces non-trivial terminology that requires learning before you can use it correctly.
tRPC, on the other hand, relies on a key assumption: Your server is written in TypeScript and co-located with the client code. tRPC has you define a server-side router for your procedures, then export the type of that router, which TypeScript automatically infers. You can then import that type to use on the client side. Since types are automatically removed when TypeScript code is compiled, there’s no extra code added to the client bundle and no extra types to slow down the type checker—just pure type safety.
Conclusion
The T3 stack perfectly aligns with my development philosophy: keeping things simple, modular, and type-safe. It’s