Transcription
My stack's been in a bit of flux lately, if you haven't noticed.
If you've been around since my channel started, you know that it all began with the T3 stack, the stack that I built my business on top of, Ping. You know, the video call app that I still use for all my collabs. That went really well. And I love the stack so much that I wanted to share it with the world. And that's kind of what led to this channel existing. I could never have guessed where it would take me. And I certainly wouldn't have guessed where my stack would end up, too.
As such, I want to take the time to break down with y'all what the current state of my stack is, what happened to the T3 stack, and how do I recommend building today because it's very different than it was before, and it's incredibly different from how I ever would have imagined it being. I'm very happy with where my stack has ended up. It has made building more fun and more exciting than it's ever been for me. I'm very happy with where we've landed, but I also think there will be some pieces that surprise you guys. From my choice to no longer really use SQL to my slow migration away from Nex.js server components and most importantly TRPC. The place I've landed is weird. The place I've landed is also really, really fun and surprisingly works well with vibe coding too, which is a cool benefit of the way that I like to think about building. Turns out when everything in your codebase is a file, not some weird config in some dashboard, AI is way better at it than it would be navigating some stupid page.
This video is going to include everything from the packages I use, the frameworks I build around, the services I integrate, and most importantly, the way I think about all of these things as I am building and designing my applications and their infrastructure. This isn't meant to be a "do this or else" so much as how I make the decisions I make as I build the stacks up for my things. And most importantly, why I love Flutter so much. Seriously though, this is going to be a fun one. I'm very excited to dive into all of this with you, but I can't give away these secrets for free. Okay, I can, but I would like to make some money. So, quick word from today's sponsor and then we'll dive right in.
It's easier than ever to go from a mock of a UI to an actual working application. You just drop it in your favorite AI tool and it will spit out the code. But how do you find the right reference point? How do you find the best place to start when you're trying to add a new view to your application? It's not as easy as it sounds. I know we've all been there. Hunting through Google images trying to find the right reference, installing 15 apps on your phone, hoping you can get the right inspiration to solve this design problem. Today's sponsor, Mobin, blew me away as soon as I first tried it. I subbed live in my ad read because I tried it and was like, "Oh my god, I need this." Mobin's built up a beautiful collection of over 500,000 screens across over a thousand iOS, Android, and web applications. Designers at tons of companies are using them, be it huge ones like Uber or small ones like T3 Chat. Yes, it's very useful to us. I'll just show you my actual account because I use it all the time. There are different categories. You can go between apps and sites. They have a whole separate section for websites now, which is great. Let's say that we want some inspiration for our shopping app. Let's take a look at the different guided tours that exist in current shopping apps. Here we can see how Walmart does a tour for how scanning works. Or if we scroll, we can see how IKEA does their shopping experience and app. You can learn from any of these super quick. And when you find something you like, you can copy the image with command C, pop into your editor of choice, command V, say, "Make it look like this," and it will make it look like that. Do you see how insanely useful that can be? Whether you're a vibe coder or a professional designer or anything in between, having good references is more essential now than ever, and finding them is way too hard, unless you're using Mobin. If Mobin cost a hundred bucks a month, I wouldn't think twice. But they're only $10. That's an insane value for what you get. And I've been blown away. There's no question in my mind that this is the best source ever for design inspiration. And if you're looking to get inspired and build better applications, check them out now at soyv.link/mobin.
Let's start by breaking down the pieces of your stack. You have the database API layer. The API layer being the thing that lets clients or even services access the data in the database and write to and from it. You have your off layer, could be rolled your own, could be using a package, could be a service. You have client framework. That's the actual code that runs on the client and the framework you use to define it. You have your style system. This can be a couple different things. It could be how you use CSS, but it could also be the way that you define the components directly. Your package management and generally workspace management nowadays. I find that npm and pnpm more and more do a lot of things that you might not have before. Package management's become a very complex thing. And then you have the additional services, things like the backend host, the database host, analytics, plus the host for that. And there's a couple optional things like monorepos. I think that covers most of it. I could stick language in here, but you know my take. If it could be in Typescript and it isn't, there's a good chance you're being irresponsible because you have to have client code somewhere. And if you could use the same language for both, that's really cool. Believe me, I wish I could just say Elixir as my answer for all of these, but I can't. I need to be realistic. I think this covers all the pieces that I want to break down here.
Let's start with the client framework. So, this one, the thing I've been thinking about a lot lately. Realistically speaking, I'm going to use React still. That's not because other things aren't good or cool so much as React is supported by everything. Every library, every compiler, every bundler, every editor, every AI vibe coding agent, everything supports React. If it can be done in the web, there's a package for it for React almost certainly at this point. It is what it is. You can have all the feelings you want, but there are few ecosystems that are both as composable and comprehensive as React is. But that's not such a simple thing anymore because React is just a piece. It's not the whole equation. And it's kind of two different directions you can go with it. Those are obviously Gatsby and create-react-app. And how do you pick between these two? Obviously, I am trolling here. The real options are Next.js and Vite. And more and more, a new challenger has been approaching: Tanstack Start. Which way, weary traveler? You guys know me. You know that I've historically went down one of these paths much more than the others. And if I'm being honest, still kind of the case. I still find myself gravitating towards Next.js for various reasons that we will discuss momentarily, but I do see myself looking at Tanstack Start in the Tanstack router ecosystem more and more every day. But Vite's in a really good state, too. And if I just want something small and simple, like if I'm doing a sandbox or a side project or anything where I don't need a statically rendered homepage, Vite can be really nice. So, we got Next, Vite, and Tanstack as the main options here.
There's another way I would recommend thinking about this though. I would think of this less as the like bundler or the init command and more as the different paths for routers. Next.js is the router for Next. Tanstack Start's router is Tanstack Router. And Vite gives you a lot of options, but realistically, the option you're picking is React Router. Believe me, I wish more than anybody that people used minimal powerful routers like Wouter more. I [ __ ] love Wouter, great piece of software, but they don't. They use React Router. React Router is in a really good state nowadays, too. Makes all the sense in the world. So, you really got to pick a router you want alongside the generative details that you want, too. Like, the first question I would ask when you're trying to pick between these is, do you want any static pages? So, you can have the classic like homepage.com and then app.homepage.com where homepage.com is made in [ __ ] I don't know, Webflow or some [ __ ] and then the app.homepage.com homepage.com is your Vite-based React application. See this all the time. You'll see it on most of the things that I talk about in my content. And if you do that, you can use Webflow or a framework like Astro or even go set it up in WordPress. Who cares? When you separate these things, you now don't have to think as much about how you make some parts static and some parts dynamic because you don't want your homepage to be a single-page app, Vappa content. You want your homepage to be an HTML file that has the correct content in it. You absolutely want that. So if you do want these in the same project, which I often do, if you look at something like uploadthing, we have a static homepage with a lot of cool dynamic parts like our ride animation here and whatnot, but it is a static HTML page. If I turn off JavaScript, it will still load and show content. But then when you go to the dashboard, it becomes much more of a traditional dynamic application. So if you want some page to be static and you don't want to do this split between different domains for the different parts of your app, your best bet for sure is going to be something like Next or Tanstack Start. Tanstack Start is still very early and figuring out the details between the parts, but it's getting there really fast. Definitely keep an eye on it. But if you really want a good minimal homepage with all the SEO and [ __ ] figured out, and at the same time have a dynamic app that is using the same code, can use the same components, the same everything, the magic that you want there is most easily done in Next.js. So if you want a static homepage and dynamic everything else, or maybe you want your blog to be built and compiled in a static set of things, that's all in a good state. And if you want to go really far with it, a thing I'm probably going to be setting up soon is multiszone. Multiszone lets you have different Next.js projects that are different sub paths on one greater Next.js project. So I can have three different package.json files for three different Next apps. One is the blog, one's the dashboard, one's the homepage, and all of them are being routed through multiszone in one Next app. Which means that your build times for your blog aren't going to affect the build times for your dynamic app. And believe it or not, the build times for the blog are often worse because parsing markdown in JavaScript sucks for a bunch of reasons. You get the point though. There's a lot of opportunity in Next to vary how static and dynamic different things are and a lot of different dimensions that they've already solved for where if you care about this, it's very beneficial. But if your eyes are glazing over at this part of the video, that's fine. Just go use Vite. Vite and React Router are a great combo and I've been using them more and more for my side projects and one-off things. They're a really good set of tools. They might not help when you want to fix your SEO or have a nice landing page, but they're very easy to work with otherwise.
So once you figured out this part, I'd argue the decision gets a lot easier. If you do want static pages, I'd still highly recommend Next. You should think about Tanzac Start. The React Router and Remix ecosystem probably won't help you much here. And Remix is in crazy flux right now. I wouldn't bet on that at all at the moment. They're about to change everything about it. React Router is still fine in a minimal enough depth. I wouldn't recommend against it, but Remix, which is the framework that the React Router team built as an alternative to Next, I'd stay away for now. They're actually moving off React entirely for the next version. So, we'll see how that goes. Cool. So, I think you understand how I pick between these two. If I want a marketing page, we go Next. If I don't, we go Vite. Easy enough.
Where do we go now? Up or down? Feel like down's a lot easier. You guys can already guess what my style system is. I have a whole bunch of videos breaking down how I think about styles. Tailwind plus Shadcn. Mind you, we've customized the [ __ ] out of our Shadcn, but same difference. You know what we're doing here? Shadcn kind of solved all the things I complained about three years ago in my previous CSS video. It's a really good combination of the behaviors of the way CSS works and your style system. They combine really well there. And Next, cuz we're here and it's easy.
Package management. I'm still on PNPM. I know. I know the Bun people. The Bun people don't have big monorepos. Bun is not a pleasant experience in a monorepo. It's made progress, but it's not there yet. And when you're using stuff like TurboRepo, getting TurboRepo and Bun to play nice when you're bundling various packages throughout, not worth it. And honestly, I find myself reaching for PNPM more and more even on single package and single package.json stuff, just 'cause I don't want to remember to switch between the two different commands. I remember back in the day there was a package called NAN that was npm and yarn and it would look at the lock file in the directory and use whichever one was for that project. And now I need bnpm, which uses Bun or pnpm depending on which one the project's using. Oh, Antfu has Ni, and I bet Ni would actually work pretty well for this too. Yeah, Ni is probably what I want. Use the lock file to make sure you're using the right package manager. Using Ni would probably get me using Bun a lot more. I should definitely consider this. You get the idea. I'm on PNPM for monorepo support. If you're still on npm or yarn, please stop. You're hurting yourself way more than you need to. Both PNPM and Bun will cache the packages you install in a global cache, so you don't have to do a network request every time you install. If you're reinstalling packages, making changes, or spinning up multiple projects, if you have more than like three projects on your computer right now, and you're not using PNPM or Bun, you're being irresponsible. Just straight up. So yeah, it's time. Kill npm and yarn. I stayed on npm way longer than I should have. I had moved from yarn to npm when it got good enough. Now I'm on PNPM always now. Make the switch.
Next part is easy enough. I'm just going to say it. I know we broke up, but I'm still on Vercel. There are so many little things they get right that are so annoying if you try to do them elsewhere. From how preview environments work to the power of Fluid Compute for a serverless environment without giving up on Node. I feel like the other options I would have would require a lot more work or compromise. Like Cloudflare would be cheaper and would have slightly better performance characteristics for things like these long-running agentic workloads and T3 chat's inference endpoints because Cloud Workers, as I've mentioned in far too many videos now, are an abstraction level higher than what Vercel does because every request on Vercel goes to a Linux instance that is yours. So it's virtualized at like the Linux container level where with Cloudflare, it's virtualized as different service workers in V8, many layers above the kernel. That means that when your request doesn't need CPU 'cause it's waiting for something else to happen, someone else's request can go to somebody else's code on the same box on the same V8 instance and resolve it. Which means that Cloudflare doesn't care about how much time your request takes. They only care about how much compute the request uses. Which means for things that are long duration, low compute like AI inference where I'm just sitting there waiting for the OpenAI API to respond with tokens, it doesn't matter if that request takes 20 minutes. I'm only going to be billed for like a tenth of a second. Cloudflare makes more sense for some of these workloads, but it's also not Node. And I know they keep making the Node compatibility better. They do, but they also absolutely [ __ ] don't. And at the same time, the process of deploying on Cloudflare is still comically harder than Vercel. If I want to put a project on Vercel, I don't even use the CLI. I go to the Vercel homepage, I click the new project button, I click the GitHub repo, and now it's up. That's it. If I want to do things on Cloudflare, I have a day ahead of me. If I'm not starting with Cloudflare's like worker templates, I'm screwed. If you're not starting with the Wrangler starting points, good luck. Have fun. And even then, the amount of hell I've went through to try and get like an API endpoint to serve next to static assets without them clobbering each other when they made the Cloudflare Pages to Cloudflare Workers migration. I had three Cloudflare employees trying to help me debug it. All of them were wrong. I had to read the source code for Wrangler to figure out why they were adding random hashes to my bundles that weren't supposed to be there. Like there was legitimately a point where I understood Cloudflare's deployment process better than the people working on Wrangler did because I was trying so hard to figure this out. And I'm not saying it's not worth it and I'm not saying it's not easy for some of you guys for some things. What I'm saying is the software I build, which has backends and frontends and they have to relate to each other doesn't work great on Cloudflare without a decent bit of effort. Hell, [ __ ] environment variables didn't work until two weeks ago. You had to use their alternative way of thinking about environments until literally last week where they finally added V support. So yeah, Cloudflare is fine as long as you're willing to put up with all that friction. But Vercel is fine, too. And now that we have the price difference with Fluid Compute, which I've talked about in many videos, go watch them if you haven't and you're interested. Vercel is cheaper than running your own Lambdas. If that statement sounds stupid, either you don't understand Fluid Compute so you should go watch that video or you don't want to admit that people can build better solutions on top of other things. It's crazy that like if we were to have a spectrum of cost where on the left is most expensive and on the right here is least expensive that previously it was Vercel, Lambda, $5 VPS, and Cloudflare. The really crazy thing that happened with Fluid Compute is Vercel moved here. It's still more expensive than a $5 VPS for a lot of different workloads, but it's cheaper than Lambda because on AWS, one Lambda resolves one request. On Vercel, one Lambda resolves as many requests as it can in the time it's alive for. They built that abstraction layer. Vercel is now cheaper. They make a ton of sense for a ton of different scales. And if you want Node infinite scale up and down, incredible preview deployments and experience there and all the other things I've talked about, they're really worth it. Still, call me a shill, you want. Vercel has cost me more money than any individual company I have ever talked about because the old sponsor deal I was getting [ __ ] fleeced. So no, I'm saying this because I believe it and moving off Vercel will be a sad day for us and honestly we were considering it heavily. When I saw how bad our bills were going to be for all the durations that we were hitting for T3 chat, I was ready to exit and then Fluid Compute happened and dropped our bill by 90%. Now I don't care that much. You get the idea. So I'm in Vercel. I'm sticking with them for a while.
Now, we need to talk about the one that if you've been around for the last couple months, you already know, but if you haven't, this might come as a huge surprise. We have DB Host and DB. They are the same for me now. So, I'll start up here. I got Convex pled. Never thought I'd see the day. I talked so much [ __ ] to those guys four years ago because previously, instead of Convex, I had a whole different set of stuff I would use. So I would use MySQL on PlanetScale and I would hit it through Prisma or Drizzle, mostly Drizzle. I would then define endpoints in tRPC. If I needed live updates, I would put those in Pusher or U WebSocket and then I would use React Query on the client to pull this all together. Notice how I didn't change anything on the Convex side. Instead of MySQL or another SQL, I use Convex. Instead of PlanetScale and other host, I use Convex. Instead of an ORM like Prisma, I use Convex. Instead of tRPC or some other API definition layer, I use Convex. Instead of Pusher for some live update layer that just comes in, I'm using Convex. And instead of React Query, I'm using the built-in Convex uh useQuery and useMutation. I [ __ ] wish they named them differently. It is what it is. There's a couple quirks with them, but for the most part, they're really nice. In particular, the optimistic update path is incredible for them. I never thought I would do this because a huge part of why I like the T3 stack and why I made it in the first place is that modular nature. The point is that you can pick which parts you want, ignore the ones you don't, and change the ones you want different things out of. And I changed them all to Convex. I don't want this to just be yet another Convex glazing video, and it's very hard to not do that. Believe me, I'm very fun at parties nowadays. You get the idea, though. Let's just go through and put Convex everywhere that they need to be here. Convex. Convex. I still use tRPC when I need things that aren't in my Convex layer. Doesn't happen that much nowadays, honestly. DB host, Convex. Well, I think that hits all the things here that are Convex.
You might be thinking about the Auth layer, though. Doesn't Convex have Auth? Yeah, and they kind of hate it. Talked to them a lot. They're not proud of their Auth package. It works kind of. I never got it working. Not sure how much of that was their fault versus mine, but it was way easier for me to use pretty much every other option. We should probably break down those options. The Auth layer. The main two paths you can take here are a service or package. You can roll it entirely yourself. And I hope you understand how stupid that is to do because there's a lot of pieces of Auth that you will get wrong if you do it yourself. You should use a package. And believe it or not, I have been pretty deep down both of these recently. For packages, you have options like OpenAuth, which if you're not familiar with is by DAX in the SST crew. They also make OpenCode. You have NextAuth, which is now known as Auth.js. I've been through it with Auth.js. I wouldn't recommend it nowadays. You had Lucia Auth, but it's dead now. Don't touch it. Cool documentation, good things to learn from, but don't use it. Then you have the most important one, BetterAuth. BetterAuth is really good. I'm also an investor, so account for some bias. We'll talk about them more momentarily. On the services side, you have Auth0. Please don't use Auth0, but you have better options, which are also again both sponsors, Clerk and WorkOS. Since this side is the easiest for me to talk about, I'm going to start with it. To put it very simply, if your goal is to get Auth working as quick as possible with all the niceties you need, like organization management, subscriptions and payments handled, the little button in the corner a user can click to have all their information come out and customize their profile and whatever, all those types of things. If you want to not think about Auth, just get it working with payments and everything handled, Clerk is going to make your life much better. I still default most of my projects to Clerk. When I make a new thing and it needs Auth, Clerk is the happiest path there by far. So, if you want everything handled for you from payments to the components the user sees, easy paths to implement most of the things you'd ever want and a reasonable price with some really good deals included like the the fact that a user doesn't count towards your user limit until they've shown up twice. If they show up once and then don't show up again for the rest of the month, the first 24 hours of a user session are free. So if they show up 25 hours later, then they count. But if they sign in once and never again, they don't count towards the user limits. Very generous. I really like that policy because it keeps a random surge of users from costing you money. WorkOS I recommend if you want to be ready for enterprise. Not that you're an enterprise company, but you might be selling to them. If you want to deal with things like SAML and Okta and all the other stuff that they handle with their admin portal, you've almost certainly seen an ad now 'cause WorkOS has bought a lot of ads on the channel. They're the solution when the company you want to work with cares more about Auth than you do. If you want a company that has a person whose job is setting up Auth to integrate into your service so that they can use it with their company, WorkOS is by far the easiest way to handle all of that [ __ ]. So I would say Clerk, if you're getting started, you want payments handled because their subscription stuff built into Clerk is really, really good. And man, I I wish it was there when we started T3 Chat. It would have made life much easier. You benefit from the built-in components and most importantly you just want to get back to building your app. These are the reasons you pick Clerk. Whereas with WorkOS, a little different. You want to sell to enterprises. You're tired of SAML, Okta, and all these other things. And of course, you want a solution that scales to sizes like OpenAI, Cursor, and more. Because yes, OpenAI, Cursor, and so many of these other companies are using WorkOS. I am regularly surprised how many businesses are on WorkOS. PlanetScale, Vercel, a lot of the other things we talk about all on WorkOS. Hopefully this makes it pretty easy to decide between these two if you do want to go the service route.
But there is a new player that complicates things. BetterAuth. BetterAuth is a very interesting solution because on one hand, it is meant to be a better open-source set of packages to build your Auth layer, comparable or comparable to things like NextAuth etc. But they're also a YC-backed startup that has plans to build services and monetize and more. I'm very excited about the future of BetterAuth. It's why I invested. They know what they're doing. They're building awesome stuff right now. They don't have the cloud platform. You're expected to attach BetterAuth to another database, probably the database you're already using for your app. BetterAuth will work with pretty much any database you would reasonably be using. It works with most web frameworks. They have support for [ __ ] everything. BetterAuth's really good, but since they don't have a hosted platform like Convex and WorkOS do, integration into other hosted platforms kind of becomes your problem, which means for something like Convex, it's your problem. Thankfully, this is being worked on. I know for a fact that both Convex and BetterAuth are investing heavily in making a happy path here.
One of the things that's so cool about Convex is their concept of components where you can have different. And when I say component, I don't mean the thing in React, kind of similar, kind of different. Components in Convex are structures of behaviors that you would want to live in your database and API layer. So if you want to add work pooling so that you can parallelize tasks but only up to a certain amount. If you want to make sure only 10 of a task can be running at a given time, you can install their workpool component that spins up like a sub table in the database that manages all of that. It's in a little dropdown in their dashboard and also gives you these pieces of code and logic that you can use in your Convex queries and mutations. The Convex BetterAuth packages are implementing BetterAuth in Convex using components, which is very exciting. The idea that I could npm install, add two lines of code to my app.config.ts file and now BetterAuth and all of the tables and things it needs are integrated into Convex without me having to maintain any of it. Very, very exciting because it's kind of the missing piece if I wanted to make a template right now. If you took even something like the T3 Chat codebase, just PNPM install, PNPM run dev, if you haven't already signed into Convex, a link pops up that says, "Hey, you need to sign into Convex to keep going." And you do it. It doesn't matter if you're on my team or not since all of the config in a Convex project is just the Convex folder. I have my image studio project here. Here's my Convex config. I have models, my schema. Here's the table definition for the project. It's just here. Here are the different models that I have access to. This is just source code that is run by all these things. The only pieces that are missing here are the API keys for the services that I'm hitting through the Convex endpoints. Specifically, in this case, Foul, 'cause that's where all my AI generation is occurring. Since all of this just lives as code, you don't need access to my Convex. You don't need access to my configurations on AWS or any of these other things. You just run npm install and run dev. Sign into a random GitHub account on Convex and you're good to go.
But then you want to add Auth, and then it stops being so simple very quickly. Stops being so simple because if you want that Auth in the database yourself, you got to define a lot of schema. And if you want the Auth somewhere else, you got to put a lot of time into dashboards. One of the pointed goals of my set of tooling is to avoid time in dashboards when building. You can spend all the time you want in your analytics dashboard when you're done building, but you shouldn't have to be navigating UIs to update your project. Both because I hate doing that. I like my source code being the source of all the behaviors, but also because AI is much better at it, too. Good luck vibe coding your way through the Supabase dashboard. That's why they have all these MCPs because it's the only way to change things. And once you have those dashboards, you also have a bunch of state you have to deal with that isn't in your codebase. That part in particular terrifies me that I could run a project at a different time and have it behave entirely differently because it's relying on some config somewhere that isn't actually in the codebase. That genuinely horrifies me. And a big part of how I design my stack is to avoid those types of things. But right now, Auth is one of those things because you need to get it from somewhere. Convex plus BetterAuth is promising the solution that I'm looking for here, which is that all of the Auth management will just be part of the Convex deployment. What that looks like is a little bit chaotic. See all of the fields that they have to add in order to handle the things that may or may not come from the user object. And you have the account separate verification, two-factor, like all of this is necessary for pretty much every user in your project. One of the reasons I like Auth services is because I like not having to deal with all this [ __ ]. But once the BetterAuth Convex integration is more stabilized, this is going to be very, very good. And I am so excited because none of this code will even live in your codebase. This is all just going to come from their package. And then when you set up Convex just by running the dev command in any of these templates, these will all be defined for you automatically. So I would expect in the near future if I'm going to do something like create T3 app again and go harder on that, it's going to happen when BetterAuth and Convex is a much more stable happy path. And I know this is the path that Convex is betting on too. They don't want to maintain their Auth components anymore. They want BetterAuth to do that and to have a really happy integration story. And God, if they figure out the Stripe part, too, it's going to be very, very good.
Speaking of which, we should probably talk about the payments part because what's the point of building if you can't get paid, right? Payments. I'll be straight up. If you're not using Stripe, you're being irresponsible. To be clear, I don't mean using Stripe directly necessarily because there are a lot of other options. My CTO is really hyped on Autumn. They're a YC-backed company that lets you configure all of your prices and all of the stuff that people can do in your app and pay for as an autumn.config.typescript file and then it will handle the configuration for both dev and prod as well as all the data management, the syncing, the making sure users are in a good state for you. Autumn is very, very promising. It's also open source, handles all the [ __ ] I've been through it with Stripe webhooks, as I'm sure many of you know. I did a whole ass video and GitHub repo on how to stay sane implementing Stripe. This went so well that Stripe actually invited me to speak at a company all-hands with the CEO. They took my feedback seriously and I'm sure they're working on it still some amount. But I am tired of waiting, as I'm sure many of you are as well. I've gotten endless positive feedback about this project. It has over 5K stars and almost every day somebody hits me up thanking me for this. It's become one of the best things that honestly ever happened to Stripe because now there's a path to do it right, to manage the webhooks, to make sure that things stay in sync and to keep it all from screwing over your database. Very nice. Generally speaking, when I have my application DB, I want the data in it to be frankly application data. What I don't want in my application DB is things that aren't application data. Those are things like Auth data. The next time I have to think about [ __ ] session tokens in my app DB, I'm going to move to a [ __ ] farm. I'm done with that [ __ ]. That's a separate concern for a separate place. Right now in T3 Chat, we have our Auth through OpenAuth in a KV that is fully separated from everything else in our app. Our Auth layer is an isolated microservice that we own ourselves running on Cloudflare that just manages the token passing and then everything else is done inside of T3 Chat with our database on Convex. In the future, we'll probably move to WorkOS because we want to sell to enterprises and it'll make that path much easier. But for now, we keep that data far away. The reason I'm talking about this now is I think the same thing about payment data. Right now, all of the data around your subscription status, the state of payments, and all of that doesn't live in our Convex database on T3 Chat. We keep all of that in a simple KV. We're using upstate of the user subscription. And whenever a webhook comes in, we update it. And whenever the user requests it and we don't have it, we double-check and write it in the KV at that time. All of this is detailed in the Stripe recommendations. If you are wanting to do vanilla Stripe, I highly recommend reading this even if you don't end up using it just to make sure your implementation methods are lining up with all the different annoying edge cases and bugs and [ __ ] you'll hit because Stripe's webhooks, they're something, man. That's all I'll say about it. All that said, I'm increasingly interested in solutions like Polar and Autumn. I don't want to deal with this [ __ ] the same way I don't want to deal with Auth [ __ ]. These are both very, very interesting projects to me. I'd recommend at least looking into the two before making a decision. Be careful with this part, too. You don't want a solution that makes it harder for users to pay for things. This is the most important part of your app is managing payments. Don't screw this part up. Put extra Karen. And it's [ __ ] that I can't just like build something into the template 'cause it's not that easy. But if I was, I'd probably put in Autumn 'cause I like those guys. I can bully them to do whatever I need them to do. Regardless, all of these things are built on top of Stripe. I'd recommend sticking with that. Oh, also, uh, Clerk's solution here is good, too. So, if you haven't picked your Auth solution yet, and you want the users to be the ones subscribing, like it's a very direct one-to-one relationship. Clerk makes that very easy to do for not too much money. Really good option. Check it out.
What do we have left? Analytics. It's a big one. And my answer's not quite as big. PostHog. I like these guys so much that I bullied them into sponsoring me. I've been using PostHog for almost four years now. They're an open-source set of analytics tools and all the things you need for building product from feature flags, session replays, and more. I mostly use the analytics. I very occasionally will use the experiments and feature flag parts. I also like the AI analytics piece, but it's mostly a wrapper around their existing analytic stuff. Very happy with these guys. I don't like any of the other options. I'll still occasionally do things with Plausible. I like these guys quite a bit. They're also open source. They're all Elixir-based, which is cool, but it's just for web analytics. And web analytics and product analytics are very different things. Product analytics are figuring out how many users go through these flows and where do they churn. If you want to know things on a per-user basis of any form, you want product analytics. If you want to know which page people go to in general and which sources they're coming from in general, that's when something like Plausible makes sense. Plausible is an alternative to something like Google Analytics. PostHog is an alternative to Mixpanel, Amplitude, and all these much heavier analytics platforms. If you don't know the difference between product analytics and web analytics, you probably want product analytics. If you do know the difference and you know you want web analytics, Plausible is really cool. If users don't sign in, you probably want Plausible. If users do sign in, you probably want PostHog. That said, PostHog's been building a lot of the web analytics things as well recently, so it's even harder to pick. You can't really go wrong with PostHog.
Let's keep gunning through these. Capture, rate limiting, etc. I have feelings on this. Other people asked, so I decided to put it in here. We're still using HCaptcha. I'm not sure how long for, though. I've gotten weird vibes talking with them in general. They really want to cut some deal where they give us a discount on T3 Chat for an ad, but those are different businesses with no overlap, and I try to explain it to them and they get like angry at me for it. So, that sucked. On top of that, I still had to implement way too much [ __ ] myself in order to make it a good experience. And the documentation was super unclear. The false positive rates are significantly lower than they were with reCAPTCHA and the integration was slightly less bad than reCAPTCHA's, but none of them are even close to good developer experience. Chat's already figured out where we're going with this. Vercel's Bot ID is a really, really good option. Obviously, only useful if you're already on Vercel, but if you are on Vercel, it's pretty crazy. There's a lot about Cloudflare that I will defend. A lot about Cloudflare that I do not like. Turnstile is such a [ __ ] show. I cannot in good faith even like say it without saying that. Like I cannot mention Turnstile without making sure you're aware of how shitty an experience it is for both developers and users. If you're not showing the Turnstile widget on the page with a big Cloudflare logo, there's like a 40% chance it's going to fail. The invisible mode doesn't [ __ ] work and they don't have an invisible to visible promotion path. So, you're [ __ ]. I cannot recommend anyone use Turnstile in its current state. I know they're working on it, but it is a [ __ ] show. Of these options, it like Turnstile cost us business investments, tons of new users, and so much more. Turnstile was a [ __ ] show for us. I highly recommend avoiding it. And it ruins the developer experience, too, because when you have it on in dev, it just randomly injects [ __ ] into the JS to test if you have a debug flag or not to know if you're on a real client. I've been through it with Turnstile. I could do like a 2-hour video about it. I'll cut it here. Just know it [ __ ] sucks. That's all I'll say. Watch my Captcha video to learn more in detail about the hell that is Turnstile.
What's cool about Bot ID is they've kind of found the balance between something like Turnstile and something like HCaptcha or reCAPTCHA. The benefit of Turnstile is that it's like entirely free. You can only have a certain number of widgets for different sites before they make you go to the enterprise plan, but it will authenticate as many users as you want for free once you have it set up. Something like HCaptcha or reCAPTCHA is quite a bit more expensive. Charges a dollar per thousand requests. What's interesting here is you might have noticed we have both of those options. We have basic analysis, which is free. That's their alternative to Turnstile. And they have deep analysis for Bot ID on Vercel, which is much more comparable to something like HCaptcha or reCAPTCHA for the dollar per thousand checks. So you have both. And they're even working on making it easier to programmatically change which one for different routes. I've been talking with them a bunch about it 'cause obviously they want us to move and honestly I really want to move. If I was building today, this is probably what I would have picked. And the integration is probably the best part. Like compared to every other solution, it's such a simple integration. Like I had to write probably a thousand lines of JS, debug a bunch of different packages, none of which worked, and spend two days sanity checking to get reCAPTCHA v3 working. If anybody tells you it's easier than that, their implementation is broken. I promise you. Whereas with Bot ID, I go to my Next config. I wrap it with Bot ID. And now when I emit it in the instrumentation client, I can specify to protect different routes and different methods on said routes. And now I'm done. Now the client is all handled. You might have to Okay, you have to put in the Bot ID client somewhere in your React code. Just be on the route. And now whenever somebody goes to one of those protected routes or sends a request to one of them, it will handle everything for you. No weird plugging between parts. You just use the component. You make the config and you're done. And
When you're ready to check, const verification = await checkBotID();
If verification is bot fail, then. This is so much better. It's so comically better.
Somebody asked, "Will bot ID still work if you turn off JS?" No. Nothing will [ __ ] work if you turn off JS.
Would you to put an anti-DOS layer? Most decent solutions handle that for you. They either put Cloudflare in front for you for the DOS protection or they built their own thing that's even better. Vercel's firewall is much better than Cloudflare's nowadays. So, I'm very happy with the one that exists there.
So, and to be very clear, DOS protection and bot protection are different things. DOS protection is making sure that when you get a huge surge of obviously fake traffic, that you block it before your code is hit. Bot protection is making sure that when your code is hit, that you're verifying if the user is a human or not in order to prevent them from sending something or doing something automated.
So for something like T3 Chat, we don't put bot protection on most of our endpoints. When you're trying to get your user data or you're trying to update something in the database, we don't check if you're a bot or not for those things. But if you're trying to do something expensive, which in our case is generate a message, that's when we run our capture checks because we want to make sure the expensive thing can't be automated and spammed. And we learned that one the hard way.
There is one other company that I've been eyeing for all of this. They're a recent sponsor and also an old investment of mine. They took a while to get all the pieces together, but they've absolutely done it now. And again, one of those things I wish existed when I built T3 Chat. Arcjet is painless security for all of the different [ __ ]. It's so much nicer to integrate than most other solutions. If you want to do like bot detection, rate limiting, email validation, DOS protection, all this [ __ ]. They have all of it with a really good SDK.
You define the arcjet helper. You can define a detect bot rule. So, this will check if it's a bot or not. And you can even choose to allow certain categories like search engines. You can have token buckets which make sure users are only doing certain numbers of things and choose how you identify a user. You get the idea. It's a really cool programmatic way to define security layers in your application. I like these guys a lot. They're building cool stuff. Highly recommend checking it out if you haven't. There's a reason I let them sponsor and there's a reason I invested. I think they're pretty cool.
All that said, if you're not on Vercel, hCaptcha or Arcjet, if you are on Vercel, bot ID is really promising. DOS protection, if you're not already on Vercel or Cloudflare, that's your problem. Figure it out yourself. Rate limiting. Every use case for rate limiting is so different. That's your problem. That's the core.
We use Convex for the database. We use Convex for the app layer because again, Convex deploys real code. Most things are going on there now. I I don't really define a whole lot of endpoints in Next.js anymore. But if I do need to for various reasons, I still reach for tRPC. ORM layer varies a lot. I am hopeful for Prisma soon. Otherwise, Clerk, Work OS. We've been over why.
Client framework: React + Vite or Next. Next if you want static pages, Vite if you don't. TanStack Query coming very soon. Style system: Tailwind and Shadcn. Watch any of my other videos to know why. Package management: PNPM. Once monorepos are better handled than Bun, we'll take it more seriously. Until then, I have no issues with PNPM. I'm very happy with it. It's one of the few pieces of software that doesn't really fail me ever.
Backend host: Still on Vercel, also for all my web assets. It's very convenient. Database host: Convex, really convenient. They do all those layers for me. And also now Convex is built on top of PlanetScale, which used to be my favorite database host. So I get all the speed and scale benefits that I would get from PlanetScale without having to do it myself.
Analytics and the analytics host: PostHog. Capture, rate limits, etc.: Vercel bot ID or hCaptcha or Arcjet. Payments: Stripe or of course the alternatives that are built on top of it, Authm or Polar.
One last piece here: file upload. Hopefully you guys know what we use for this. UploadThing. I will say Convex solution is pretty good. The catch with Convex is that it doesn't handle public versus private files really at all. UploadThing is a much more inclusive file management and uploading solution. Similar to how something like Prisma is significantly better at ORM than Convex ORM is. I'd say they're very similar in that regard. The difference being you don't have to config to get file uploading working if you're doing it with either tool. You do have to configure a lot to get ORM working regardless of what you're using. UploadThing will give the best experience for file upload for your users, for fetching said files, for managing private files, for scaling to large numbers of files, for making the DX of when a file upload is completed and all of that good. UploadThing will be the best solution by far without [ __ ] your bandwidth as well. Convex's file upload is fine, too, though. Hell, in the image studio I'm building right now, I'm using Convex ORM just 'cause it was one less thing to add and it made stuff pretty easy to do. I even know where that would be configured. Okay, the more I'm looking at this, I'm realizing that I really should have used UploadThing because like all of this code to fetch the image, make sure we got the right HTTP response, convert the blob, make sure we have the file size annotated properly, all that. We handle all this in UploadThing. You hand us a URL and we'll process the file for you. So, I probably should have used UploadThing for this. But at least I can call context.storage.store the blob and then grab the URL by doing context.storage.get URL. This is also the thing I was talking about before though where this URL you get is a permanent URL. You can't really do a temporary signed URL or anything like that. So checks and balances.
The goal of everything above is that you can use these regardless of what your answer is on all these parts, but we can go into these parts. Monorepos are a necessary evil. I still wish they were much better. Turborepo made it much better. Still not where I would want it to be, but at least it's not the hell it was before.
Mobile: You guys know we'll be on Expo and React Native for a long ass time. And if you want a really good showcase of a React Native Expo app, keep an eye out. T3 Chat's going to be better than every other AI chat app, and it's going to be built in React Native.
Effect: If you need it. I'm starting to get Effect-pleased. I I know the value, but it's also caused us enough problems like like, okay, to be frank, I only see the Effect stuff when it's causing us problems because I'm not in the details of the code bases anymore. Like by the time Julius is effectifying the codebase, I've kind of had to move more to a management position anyways, but Effect's incredible piece of software. It's capable of a lot. It has made things much better for us around error management. And god, the [ __ ] stuff that we can do with introspecting on traces, the UIs we now have access to to see every single thing that happened during a generation or during a user request all in Axiom just with the traces in Hotel that were spitting out of Effect for free is incredible. So it's absolutely been worth it for us.
KV store: I find that the use of KVs is always some specific integration thing. Like the KVs that we're using right now for T3 Chat are Worker KV for OpenAuth. So we have that isolated Auth thing. Ideally, you'd have an Auth solution that doesn't need that. We also use one for our Stripe storage which is Upstash. You don't need that because you have a payment provider that handles it, something like Authm or Clerk or Polar. So again, if you need it.
And then the IDE: I'm still on Cursor. I find that it matters less and less every day. Other tools are catching up. Things like KiloCode, RuneCode, Client, and more are making VS Code really good. The fact that C-Pilot's now open source and more and more of what makes it work well in the editor is accessible to extensions. A lot of progress is being made in the IDE space, but Cursor is still my best experience. That's the one that's the most reliable that does the most. Even though I love Tmux in my CLI in general, I like managing my projects in my CLI. I don't like writing code in it. Still was never a Vim guy. So, I'm not into stuff like Cloud Code, Open Code, ADER, CodeX, and all that.
And I guess one last one: background agents. My answer to that one is TBD. I've not been happy with any of the current solutions. I've tried most of them. They've not been good for me. Maybe in the future, we have something for that, but right now, meh.
There's a bunch of other things that I use all of the time to make our teams more efficient. Things like Discord for all our comms, Graphite for all our code reviews, CodeRabbit for leaving feedback in said code reviews, so much more. We've already went really deep on things that aren't necessarily my stack. I want to make sure the value and the focus stays up here. The pieces that I recommend building with and that I personally choose to build with today.
And once again, the point of this isn't that you should copy paste it and use it for all your projects. The point of this is to show you how I think about the pieces I use for my stack and to look at the ones I tend to reach for and figure out which pieces are useful to you and which ones are not. If you take this whole stack, copy paste it and use it and find success with it. Awesome. I'm really happy to hear. But never feel bad about deleting parts that aren't necessarily as useful for you, swapping things around based on what your needs are, and doing whatever you need to stack. The point of it is that it's modular, that all the parts are meant to be swapped and interchanged. Even as I went through, I showed you how I swap different parts around depending on what I'm building. I'm much less interested in people copying my stack and much more interested in people thinking about their stack in this way. A more modular approach around what you're trying to build and how you're trying to assemble those parts together. This is how I assembled them today and it's very different from how I did last year and it's very different from how I did it 3 years before. And hopefully this way of building things is useful to you as well. Hope this was helpful. Can't wait to figure out what database I move to next.