Get AI summaries of any video or article — Sign up free
My current stack thumbnail

My current stack

Theo - t3․gg·
5 min read

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.

TL;DR

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?

The guiding rule is to keep complexity proportional to necessity: start with the simplest reasonable option, add complexity only when real constraints force it, and regularly reflect to see whether that complexity is still required. When layers become painful, he prefers deleting and rethinking over patching—he describes rewriting major parts (including data sync layers) when the maintenance cost outweighed incremental fixes.

How does create T3 app embody modularity in practice?

create T3 app scaffolds a Next.js + TypeScript setup by prompting for modular choices: Tailwind (yes/no), tRPC (yes/no), NextAuth/NextAuth-like auth choice (NextAuth is referenced as “next auth” option), and database options (none/Prisma/drizzle). The goal is to avoid installing tools that aren’t needed and to allow swapping one part for another later without rebuilding the whole foundation.

Why does he treat KV vs SQL as a staged decision rather than a permanent choice?

KV is favored early because it’s simpler and can handle many product needs efficiently; moving to SQL later is framed as manageable when relational modeling becomes necessary. He gives a concrete evolution for T3 chat: it started with Redis (Upstash) and later moved to PlanetScale once per-user data (threads) grew to hundreds of megabytes.

What’s his “Stripe done right” recommendation?

He recommends syncing Stripe’s operational data into a KV store (not the main relational database) and using that KV as the source of truth for subscription status. The motivation is to avoid turning the primary SQL schema into a payments-specific maintenance burden, making subscription logic easier to keep correct over time.

When does he prefer server components over client-heavy patterns?

He likes server components when the core of the app can be static or mostly server-driven—he cites projects like marker thing as largely database-free and static after the stream ends. For interactive, dynamic flows, he relies on client-side async/data patterns and uses React Query (often paired with tRPC) to manage fetching and prefetching to avoid UI “pop-in.”

What’s the rationale behind using React Router inside a Next.js app?

He wants navigation and interactions that don’t block on server work. To achieve that, he uses React Router (with V4-style syntax) inside Next.js, then adds custom rewrites/headers to prevent tRPC endpoints from being intercepted by Next’s catch-all routing behavior. He emphasizes this is a hack—useful for his constraints, not a universal recommendation.

Review Questions

  1. Where does Theo draw the line between “start simple” and “add complexity,” and what signals tell him complexity has become unnecessary?
  2. How do his data-layer choices (KV first, SQL later) map to the kinds of product features he’s building?
  3. What specific strategies does he recommend for payments and for auth, and how do those strategies reduce operational risk?

Key Points

  1. 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. 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. 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. 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. 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. 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. 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.

Highlights

The most repeated lesson isn’t which tools to pick—it’s how to keep systems simple enough to survive: delete and replace when complexity becomes maintenance debt.
KV-first, SQL-later is treated as a sustainable migration path, not a one-time architectural bet—he cites T3 chat’s move from Upstash Redis to PlanetScale as data grew.
His Stripe guidance is operationally focused: sync Stripe data into KV so subscription checks don’t force payments logic into the main SQL schema.
He prefers PostHog when he needs product analytics tied to user actions, not just anonymous page views.
He uses React Router inside Next.js to keep navigation responsive, then adds custom rewrite/header logic to prevent tRPC endpoints from being hijacked by catch-all routing.

Topics

  • Modular Full-Stack TypeScript
  • KV vs SQL Migration
  • Stripe Subscription Architecture
  • Auth and Analytics Choices
  • Routing Workarounds

Mentioned