My current stack
Based on Theo - t3․gg's video on YouTube. If you like this content, support the original creators by watching, liking and subscribing to their content.
Theo’s stack philosophy is iterative: start with the simplest reasonable tools, add complexity only when constraints force it, and regularly delete or replace brittle layers instead of endlessly patching them.
Briefing
The core takeaway from Theo’s “current stack” rundown is a decision philosophy: start with the simplest reasonable building blocks, add complexity only when a real constraint forces it, and then keep deleting and trimming until the system stays maintainable. The stack itself is modular—React, Tailwind, tRPC, React Query, KV stores, SQL where it truly pays off—but the real through-line is how he prevents “state sprawl,” duplicated data, and brittle integrations from turning into long-term operational pain.
He frames his approach around T3’s modular full-stack type safety: create T3 app lets developers choose pieces like React vs. other options, Tailwind, tRPC, Next.js, and database choices (none/Prisma/drizzle), with the intent that teams can swap components without being locked into a single monolithic framework. That modularity matters because his projects evolve. Some apps stay simple enough to avoid heavy client interactivity; others demand richer data fetching, permissions, payments, and performance tuning.
On the front end, React is the default, but he’s explicit that he doesn’t reach for UI libraries when a project doesn’t need them. In minimal cases, he’ll ship plain TypeScript and HTML snippets. For authentication, he often uses Clerk because it’s the fastest path to production-ready auth flows and user data handling, but he also uses OpenOff when he needs a more separated “auth as a service” model that doesn’t run between every request. Analytics are similarly pragmatic: PostHog is his go-to for user-specific product analytics, while he uses Vercel Analytics or Plausible only when the use case fits.
The biggest architectural lessons come from how he handles data and state. He repeatedly prefers KV stores early because they’re simpler and flexible, then moves to SQL when the product’s relational needs and scale justify it. For example, T3 chat began with Redis (Upstash) and later moved to PlanetScale as per-user data grew. He also warns against gambling on database platforms: he recounts multiple Torso incidents and data-loss/incorrect-data scares, and he argues that reliability matters more than low cost.
Payments and subscriptions are another complexity magnet. His guidance is blunt: keep Stripe’s operational data out of the main relational database by syncing it into a KV store and using that as the source of truth for subscription status. He also describes how he refactored parts of T3 chat after learning that his earlier Stripe modeling and sync layers were too painful to maintain.
Routing and backend integration show his willingness to use “weird hacks” when they preserve user experience. In Next.js, he uses React Router (including a V4-style syntax) to avoid server-blocking navigation, then adds custom rewrites/headers to prevent tRPC endpoints from being intercepted by Next’s catch-all behavior. He’s candid that this is not a recommendation for everyone—just a pragmatic solution that has scaled for his team.
Finally, he ties the whole stack together with a consistent tooling pattern: TypeScript everywhere, React Query as the async/data layer, tRPC for type-safe backend/frontend relationships, and server components where they reduce client complexity. The stack is chaotic across projects, but the method is consistent: keep the system simple, reflect on pain points, and replace brittle layers rather than endlessly patching them.
Cornell Notes
Theo’s “current stack” is less a shopping list and more a repeatable method: begin with the simplest reasonable choices, add complexity only when constraints demand it, and aggressively delete or replace parts that become too brittle. His modular T3 approach (via create T3 app) lets projects swap components like Tailwind, tRPC, Next.js, and database dialects without locking into a single monolith. Across apps, he defaults to TypeScript + React, uses Clerk or OpenOff for auth depending on how much he wants auth to run between requests, and prefers PostHog for user-specific product analytics. Data strategy follows a similar rule: start with KV (often Upstash/Redis) and move to SQL (often PlanetScale with drizzle) when relational needs and scale justify it. For payments, he recommends syncing Stripe data into KV rather than overloading the main SQL database.
What principle drives the “stack” decisions more than the specific tools?
How does create T3 app embody modularity in practice?
Why does he treat KV vs SQL as a staged decision rather than a permanent choice?
What’s his “Stripe done right” recommendation?
When does he prefer server components over client-heavy patterns?
What’s the rationale behind using React Router inside a Next.js app?
Review Questions
- Where does Theo draw the line between “start simple” and “add complexity,” and what signals tell him complexity has become unnecessary?
- How do his data-layer choices (KV first, SQL later) map to the kinds of product features he’s building?
- What specific strategies does he recommend for payments and for auth, and how do those strategies reduce operational risk?
Key Points
- 1
Theo’s stack philosophy is iterative: start with the simplest reasonable tools, add complexity only when constraints force it, and regularly delete or replace brittle layers instead of endlessly patching them.
- 2
create T3 app is designed for modular full-stack TypeScript: it scaffolds choices like Tailwind, tRPC, Next.js, and database dialects so teams don’t install unused complexity.
- 3
He defaults to TypeScript + React, but he avoids UI libraries when a project’s UI is simple enough to ship as plain TypeScript/HTML.
- 4
For analytics, he prefers PostHog for user-specific product analytics (events tied to user actions), while he treats generic page-view analytics as a different category.
- 5
His data strategy often starts with KV stores (e.g., Upstash/Redis) and moves to SQL (e.g., PlanetScale with drizzle) when relational modeling and scale justify it.
- 6
For Stripe subscriptions, he recommends syncing Stripe data into KV and using that KV to check subscription status, keeping payments complexity out of the main relational database.
- 7
He uses React Query as the async/data layer and tRPC for type-safe backend/frontend relationships, and he’s willing to use routing workarounds (React Router inside Next.js) to avoid server-blocking navigation.