T3 Stack Tutorial - FROM 0 TO PROD FOR $0 (Next.js, tRPC, TypeScript, Tailwind, Prisma & More)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
about a year and a half ago I made my first ever video where I coined the T3 stack in that video I made an app where you can vote on which Pokemon is most round using all the tech that we love here next.js Prisma Planet scale trpc for cell all the usual stuff but I wanted to go in further the stack has changed a lot since the T3 stack is its own big viral thing I've seen dozens of other creators making tutorials of their own but I wanted something official and I put a lot of effort in for this so we're gonna build a full Roundup clone of Twitter from scratch with one catch you could only post emojis I think this is going to be really fun it's a great highlight of just how much you can get done with the stack from zero to production ready I went out of my way to make this tutorial both beginner friendly and useful to experts make sure you watch to the end because there's some really cool stuff I don't want you to miss about how to take this Tech and push it really far in production everything here can be done by any developer and can be shipped to the scales of millions this is the exact same stack we see companies like Cal using obviously for sale my myself at ping and I can't recommend it highly enough so quick notes about the services we'll be using today they all have insanely generous free tiers like you can scale to tens of thousands of users generous freakers I don't know how they stay in business on top of that I've been deploying on all of these services for months if not years some of them for the better half of my career and also these brands are all sponsoring the channel they're part of the T3 deploy partner program they are not sponsoring this video they have no saying what we're doing in here I'm using them because I like them but they do sponsor me in order to support the channel I hope this is helpful we've put a lot of effort into this tutorial without further Ado let's get started with the official T3 stack tutorial building an emoji only Twitter clone with all new projects I start I tend to do the same first three steps first initialize second get it on GitHub and third get it deployed I like to deploy my applications really early during my development process so I can be sure everything's working in production that way if I run into problems later on I can go back to the latest working version and figure out what changed since I find that if you wait to deploy it becomes much harder to understand why things are going wrong so make sure you deploy early and often in all of your development process as with almost all projects I do nowadays we're starting with a great Community created tool create T3 app you haven't seen it before create T3 app is a fantastic way to get started with your next JS project comes with all of the T3 stack Technologies at least the open source Parts we're not going to use all of them in this project and I'm going to show you how to replace the ones that we're not using with the ones that we will be the goal of this stack is to be modular and type safe without compromising on the scale of your applications it's one of the easiest ways to get started and build really powerful applications so let's do it literally can just click copy go here so when we create with create T3 app you have the options to choose between JavaScript and typescript we're going to choose JavaScript because I want to show you it's the wrong option you can actually only pick typescript as much as I love next stop I have grown to prefer third-party auth Solutions we'll be using clerk for this video we'll get into that in just a bit but for now we're going to check all the other options because we are using Prisma tailwind and trpc so we select all those you want to initialize a new repo or npm install sure it's a fine Alias and in just a moment we're going to have a fantastic setup and for those inevitably asking why not pnpm or yarn they're fine pmpm is better yarn's kind of dead but none of it matters I'm using npm because it's the default everyone has haven't installed anyways it's it's the easy one so we're using npm anyways we're gonna go into the project chirp gonna use the shortcut to open up vs code and here we have all the files for the project the first thing I like to do as I mentioned before is commit getting started and then we need to put this on GitHub the easiest way to put it on GitHub honestly is to go to the GitHub website on the top right here which you're not going to be able to see because my little thing in the corner is covering it is a little add button a little add button new repo owner myself I'm part of a lot of projects that's why that option is there we'll name this chirp gonna make it private for now we can make it public after the tutorial is done and we specifically don't want to check any of these options because we already have the repo on our machine and those will make a new repo with stuff in it so we can push an existing repo copy the contents here face it in our terminal and now this is up on get up if I refresh here you'll see the project is here so let's run on our machine make sure everything works npm run def end this up everything connects fine hello from trpc this all looks good let's get it deployed really quick as well so how are we going to deploy it you don't know how we're going to deploy it orcel.com ignore the absurd amount of projects I have in here including something very similar to this one that I totally didn't build from scratch to make sure I could do this project well new project and if you haven't already signed into Versa sign in with your GitHub account you'll immediately have access to all of your GitHub projects including the one we just made import scroll down click to play we don't have any environment variables yet but if we did they're very easy to add I can go to the environment example here copy you can copy the contents of this file and paste and it will automatically split it accordingly we don't actually want those variables we're totally fine for now but deploying I do think something's going to go wrong when we deploy though just as expected we're getting an invalid environment variable this is one of the coolest things create T3 app does the file is a little complex but don't let that scare you inside of the environment.mjs file we actually validate the environment variables that you're running with so if you forget to include in this case your database URL you're going to get an error in production or wherever else you're building letting you know that this value doesn't exist when it should we already have it in our DOT EnV here which is why we didn't run into that error but in production we don't have a value there more importantly though the database URL we're using here is for sqlite and we don't want to use sqlite we want to use Planet scale because we want a database that scales this is a production ready application after all so let's go initialize a new database on planet scale planetscale.com signing in now in my personal and create a new database it's important to make sure that the location of your database is the same or as close as possible to where your cell functions are being deployed I'll be using Us West 2 for versa so I should also use us West 2 here we'll name it chirp DB cool now we have a database for Sharp and in here we have methods to connect there's actually an option you can click here for Prisma that will tell you exactly what you need to put this is a password so I'm going to reset this after it's important to know when you're like live or showing this code to other people that these values should not be publicly shared and they shouldn't even be committed when I put this inside of here you'll notice this file doesn't trigger anything in git we actually get ignore the dot EnV on or in create T3 app because it is so important you don't accidentally leak these values by committing them so we ignore this definitely the right call we've gotten some arguments about it but trust me you don't want to commit your vibrant variables it's not worth the risk if we go back to the dot EnV we have the database URL but we also make other changes for Planet scale because right now it's set up for sqlite and we want it to use Planet scale MySQL so if I search Planet scale for cell or sorry Planet scale Prisma quickly Prisma quick start scroll I already have that all installed and here is what I was looking for any oink that we're going to go back here we're going to hop into the Prisma file schema.prisma this is what Prisma uses to generate and connect to your database very important more importantly we need to replace that so now instead we're going to use MySQL we're going to use the database URL from environment we're going to use Prisma for relations because Planet skill doesn't have traditional relations in MySQL because it's using the test under the hood it's a bunch of technical mumbo jumbo just know that the way that they scale you need to build your own relations more strictly with IDs but you can use Prisma to make it feel almost identical to any other relation system is equal really simple stuff once it's set up don't think too much about it I promise you it's not super important just yet so now that we have this all set up and the environment variables changed I should be able to npm run Dev and connect fine here more importantly since we're using Prisma I should be able to npx Prisma Studio which gives me access to the DB directly when I click here we're actually going to get an error because the database is not synchronized with what we have in our schema our schema expects the database to have ID created at and updated at with this model example but we haven't created any models in the database the easiest way to do that with Prisma MPX Prisma DB push this tells Prisma to take the current state of things in your schema and set that to the database wherever it is usually I would use a separate Branch for doing my development from the production database Branch another cool planet scale feature but for now doesn't matter just going to synchronize to production same database for both it's not too hard to deal with now that we have that done we should be able to npx Prisma Studio again and this time there are Narrows in the table and from this UI we can add new rows if we'd like super cool feature Prisma studio is one of the coolest parts of Prisma I got a ton of value out of that we'll be using it a bunch later in the tutorial more importantly we can now push these changes and deploy so first thing get status when I'm adding on git I like to use Dash p after git add so I can pick the parts I want to add it's like a mini code review as you're doing your changes it's really helpful gonna add that get commit move to mySQL now that we have done that I am able to grab the environment variable we have here yoink hop back here go back to first cell refresh settings environment variables paste production normally you'd want a different branch for development but I'm just going to use the same one for everything it's way easier for now just a tutorial but ideally you want to keep those environments separated and now if I was to redeploy or actually it should not be redeploying if I pushed oh no that reader play is going to fail because I did that before so since I did that too early I have to go in here and manually redeploy but this time since the environment variable that was missing is provided it should deploy without issue and look at that generating without issue wait just a moment longer we should have a production URL that works there it is if I go to chirp rho whatever that is ta-da we have a production app on the web gonna go make sure I have the functions deploying in the right place quick that's in settings under the project go to functions you can pick and look at that it's not we want this in PDX because that's Portland Oregon which is Us West too changing that saving that now it'll be updated the next time I deploy nice and easy and we're done with that half cool but we still need one more piece clerk has been way easier to manage auth especially if mobile is also a problem you have to deal with really happy with the service I couldn't recommend it highly enough let's get it set up super quick we're going to go to Clark dot Dev already signed in so I can just go straight to the dashboard we're going to create a new application I don't like having email as a authentication method I very much prefer to not do anything password related so we're gonna not I also don't really want to Google sign in what I want more than anything is just GitHub we're gonna hit the GitHub switch we're going to turn off that other switch promise you looks fine on my screen this is just my contrast being weird for my screen recording cool once you've picked your authentication methods that you want to support you're pretty good to get going I'm going to be lazy and go through the next guide to set things up because it is that simple there's a bunch of stuff like callback URLs that are normally obnoxious to set up that clerk handles for you you don't even have to set up keys for oauth when you're still setting up you can switch them in production later so for now we're going to just follow along with this guide oh the new docs will follow along with that we're not using App router just yet so we're going to use the normal next.js guide first thing we have to do is install Clerk copy paste well that's going on we're going to go back into the dashboard in order to grab our API keys and these are for EnV so I can copy go back in here paste more importantly I can go to for cell and paste the same values settings environment variables paste save and now we have those saved there if I did everything here correctly then we should be able to get started so I'm going to go here to the clerk provider example where we wrap app with the provider the value of wrapping your app the core provider is it means every component in the application has access to your authentication State really convenient for knowing if you signed it or not in rendering the right content so we can go hop into the app file under Pages underscore app this file is kind of like a component that wraps your entire application very convenient to have something like this in your app but uh yeah dangerous to touch this component because when you do everything you do to this affects everything in your app but clerk kind of should affect everything in your app because your authentication state is important autocomplete that import bank that's all we have to do here yeah nice all done I think they have a middleware yeah to use server-side functionality you need to add the clerk middleware to your next JS app here's where things get fun for the stuff we're doing with create T3 app this will allow us to have authentication when every server request is made by embedding the auth state inside of the request itself via the request we know if the user is author not based on their cookies but we don't know how to process that without doing it on each request and rather than manually doing that when the request is received we can do that it's part of middleware on an edge before it ever hits our own servers but if those words went over your head don't worry about it you're good I promise so we need to go into basically anywhere in here can I just put it in Source middleware.ts it's a magic file name that tells next.js hey this is a middleware just run this before everything else can delete all X we don't need it and I think we are done cool now middleware.ts will run on every request we can console log here to be sure not running and you can see here middleware running because that request is processed because every request gets processed through the middle right now awesome huge step since we have this all done should probably add a sign in button make sure it all works right let's do that kill all these things we don't need anymore now there's a signed in component we'll just use this for now go to Pages Index this is the home page we can delete most of the contents here and it doesn't render anything else I put stuff in and I'm assuming no that's a sign in button this seems fine as a quick test oh I was working before I think yeah now you have the sign in button we can click that you have the option continue with GitHub or username I don't want username as an options so we're gonna go back to the clerk.dev settings and make sure we have it set to only let you sign in with oauth go to chirp we go to user and auth email phone off turn this off too yes we don't want any contact info we want the only method of auth to be GitHub if I refresh here now look at that only GitHub is an option nice and convenient continue with GitHub failed to construct URL invalid URL interesting that might have been because I refreshed or it could be because I already had a cookie do that again cool no error this time probably just because I did the weird refresh in the middle but right now we're not doing anything if we're off there now we have no way of knowing so we should do is const user equals use user and then I can I like to double knot when I do the syntax in react so if yes if not not user which means if there is a user or if there isn't a user we do a sign in button and we'll do the opposite for our assign out so there is a user sign out button oh is it because user yep it's because user has properties on it so it's user is signed in my mistake there we go now the sign in button renders when we're not signed in click that and now it's a sign out button instead ta-da so much better my bet on that anyways now that we have this all working might as well get stuff started on the server right let's do it get status get add get commit add auth and now we have our application authenticated up on the server ready to get rolling so let's get rolling sorry can't break you know how it is he gets needy I've been trying one all week so he's missed me a lot one more important piece you want to make sure we know when things go wrong in production as much as I love the new for cell logging system I do want something a little more thorough and when I'm looking for something more thorough I go with Axiom Axiom is incredibly easy to set up you can sign in use GitHub or whatever else I multiple accounts because I use it for my company as well so I'm going to sign out of my company account so I'm going to sign in with my GitHub instead Oregon not found because that's not the one I'm using here's my open source org we already have Versa linked if I didn't it would be two clicks but I'll show you what those clicks look like I'm gonna go to versl settings sorry Integrations and once you get into durations you can just search Axiom click add pick the org I only have one or you should probably only have one I have a lot you can pick if you want it for specific projects or all of them in this case I'm just going to pick a chirp because that's the only project I need this for connect and now all of the logs for this project will go straight to Axiom where I can search through them and find them way more easily really bud let's actually build the application now that was all fun stuff but uh we gotta write some code don't we thankfully when things go wrong it'll be very easy for us to identify when they went wrong and how they went wrong well we have everything else set up so first off we actually need some data to render in order to have data we need to have a database let's hop into the database and we're going to make a new model for a post right now we have this model for example which is a good starting point change this too post I don't really need to get update it at I like using cuids for created art it's a nice format because it is consecutive while still being unique IDs pretty convenient you can make it anything technically but I'll just leave that as the default we have to create a DOT we need the content which is also a string and in planets or in my sequel the string length is weird so I'm going to manually set that to a very care of 255 which means that it could be 255 characters long since they're emojis a lot of them count as two characters managing that length will be annoying but uh we'll get there when we go through we'll only one else yeah both need a user ID which will be a string I think that's it yeah and that's it I guess we'll call it author ID it's more accurate you need to be able to look up all posts by a given author which I have to go through all of the posts to find them the easiest way to do that is to create an index so in this case at index brackets author ID that's all you need to do and now this will be index based author ID we can look up by that instantly instead of having discovering or instead of having to scavenge the whole table we don't really need example anymore so I'm going to kill that and you need to run two more commands MPX Prisma DB push it's going to push the updates to Prisma it says we're going to be dropping a table which is totally fine but we also need to generate the new DB State locally because this file isn't typescript so our typescript code doesn't actually know anything's changed easiest way to do that is npm install you can also do MPX Prisma dbgenerate but npm install will trigger that automatically and now you'll see if we go into here we're actually going to get an error on this get all call because dot example no longer exists because of course it doesn't we deleted it so get all now we wanted to return posts if I go here we'll see post is now an option so restart typescript NDS lent quick seems like they're out of date cool that's the typescript error if you're not familiar a typescript server error if you're not familiar typescript runs a server in the background if you ever get an error and doesn't seem right command shift p and you can restart the typescript server and you should see things behaving as expected and now if I go back to the front end code here you'll see we got all this junk back more importantly if I console.log hello.data hover over this you'll see data has a greeting string or undefined which is uh not what we're looking for because this is API example hello use Query and we don't care about that what we want to do is const data equals API Dot example dot get all dot use Query and now data should have the correct type of post array so if you're not familiar with trpc the syntax might look confusing the tldr is trpc unless you create server functions that run on a server in this case a for cell server to actually do things like fetch data from database so that you can get that data in the right shape authenticated and whatnot to your user without having the user run the database code themselves you never want a user to connect directly to a database when use something like for sale it's way easier to deploy that connection method somewhere else and here with trpc it's just as easy to access that code even though it's living on an entirely different server than the computer the user is using and in order to get to that backend code you can literally command click and here it is we currently have a router named example if we go to the trpc root here you can see all that means is we have example example router we could rename this to whatever or create our own which is what we're going to want to do here we're going to create a posts router again routers are just an arbitrary way to break things up we could put everything in one router I like this way of breaking things up though so we're going to do it and copy paste let me rename this two posts router we don't need hello anymore we still can use it get all so we'll keep that for now I'm not using Zod just yet but we will be in the future a procedure is in here we have a router which we use the create trpc router helper to create and then we have a public procedure which is a method to generate the function that your client calls public procedure is a procedure that anyone could theoretically call without being authenticated in this case we do want get all to be public because we'd want anyone to have access to all of the posts if you can should be able to open the homepage without signing in should be nice and simple to work with now that we have this set I can grab post router we can go back to the root I can make a new key here posts post writer that doesn't exist because it hasn't been imported but we can do that I can delete example router honestly we can just get rid of it it's not being used for anything cool and now we have this get all key go here doesn't work but the magic of trpc is this will all autocomplete from here dot posts dot get all dot use Query and again I can command click and it brings me to the back end code from the front end code these two different files run in two different places this one runs in the user's device this one runs on our servers but we can still go back and forth between the two like they're on the same machine it's such a convenient developer experience and it lets us move very very fast so now we're fetching all these posts we should render them somewhere I'll just put them under here div ATA dot map post div post dot content they have cool these are gonna need a key so post.id keys are a way that react uses to identify what should or shouldn't be updated it's not super important but it will keep the amount of time sets to render down slightly so it's not the worst idea now for every post we'll have that render if I was to go to localhost really quick now you'll see nothing new because we've made any changes to make it appear there's no posts in the database yet so why would that appear so how do we get a post I could go write all the code to make a post but I mentioned before prisma's studio is very useful and this is what I was referring to so if I go to MPX Prisma Studio since we may change it to the DB we have to reset the environment for our Dev so close and reset the dev environment when you make those types of changes just to make sure everything's up to date now that we've done that I should be able to load this and not get weird errors anymore more importantly I can go in here we can go to post we can add a new record first we need content so we'll give this some eyes really save oh no I forgot to give it an author ID oh it didn't even give it the content this yeah Prisma I love you this tool is so awful you have to click out to validate that save and now it has the eyes in it we'll get the author ID later because we want to see there we are and now new posts are rendering here the goal here wasn't necessarily to make everything perfect it's to show you that you can use Prisma Studio to quickly access and make changes so you don't have to build all the environment just to test something quickly like rendering a post more importantly though now that we have that working get status get add select select select add Dash p does not add new files so I have to add this manually now that I push that with versel all new pushes will automatically deploy and go in here we'll see in the next 40 or so seconds this should be deployed and working as expected pretty magical and now we have the updated version you can see the eyes are right there where they belong awesome we've made so much progress in not a whole lot of time really proud of that I was actually finished fill in the app though so we should start we haven't really done anything even resembling styling so let's get to that first thing I want to do is Ditch basically all the Styles here I'll just delete that for now I do like to put some Styles in the body like I want to have the global background color and such because if you don't put that in the CSS file then it won't affect the first load and it will only work once this page loads and I want this to work always and here we have the Styles Global file right now we're just importing the tail and stuff all obviously set up for you by create T3 app super convenient we want to do a little bit more so we're going to have body at apply BG black text we'll do slightly lighter than or not white but a gray I'll I like slate recently so we'll do slate 100. if you're not already familiar with the Tailwind colors they are incredible if I type in tail into the colors autocomplete because I spend so much time on this page just ripping ripping uh God hex values sorry words are hard such a useful page all the tail and docs are great but man the color system is incredible I steal it for so much stuff and now if I go back to localhost you'll see we have black background with white-ish text as much as I love how this looks we can probably do a lot more with the Styles so uh let's do that quick close on all the tabs I'm not using we should try and clone the Twitter classic centered view we will do that quick prefer to do that here we could do it in app since it will affect every page we don't necessarily want to change every page you pick where you put it based on what you do or don't want it to affect I'll probably just put it here and we can reuse it we can even make it a reusable component in the future but for now let's put it all in here love keeping things in one file makes things nice and easy so main class Flex uh justify Center div it's going to be like the main contents div and to make sure we're modifying this correctly class name equals BG red 200. that way we can see it against the background here this needs a width so [Music] W full Max W 2XL now it's going to be up to with 2XL but we can also do here is make that only happen when it's a certain size so now we'll only have Max with 2XL if the size is medium or larger so on a smaller scale it won't do that I know the pink background's annoying but it makes it way easier to know if things are working correctly also want the border so border LR is there Border X curious there is that's so convenient God every time I use Tailwind I'm like amazed at the other things it has I didn't know about kill the background color so we can see it more clearly it's now apparent the height isn't high enough so H full that doesn't do it we need the H full here and that doesn't do it either because the body isn't big enough uh easiest solution here is going to be H screen these now contradict we'll get an error there because we have talent linting super nice and ta-da we now have the Border edges correctly placed the Border color there is a little bright though so we're going to dim that down slightly let's border slate we'll do 200. yeah just make sure I'll try 400 see if it looks better oh yeah the 400 looks better I like the the dim there for that but things are way too close here I don't want to add padding there because I want to be able to do it inside elements in the future but we should have this top nav be split in some way class name equals B or border B border slate 400. now it has a border on the bottom it should have padding P4 Flex justify and we don't need to adjust if I Center this all div and Center class name equals Flex justify Center instead of a sign up button we should render an actual like header component we'll get there in just a bit but now yeah I will leave the sign out for now we should split the tweets better too class name equals P we'll give it a bit more space border B order slate 400 so that there will be a line under each one and this should be flat or class name equals Flex Flex call so that it is a flex column when we add additional elements in the future we will have more easy way to test that is to do I'll say both exist even when they don't and if no data returned I guess this is a good time to introduce you guys to one of uh the cooler features in react query I can have an is loading check there and if no data or is loading we return loading ideally what you do is if is loading return loading and then if no data return see if copilot's smart enough cool it is something went wrong and now we're sure by the time we're here that we do have data so it shouldn't be mad at me for that anymore because it knows data has to exist at that point super convenient I just did that quick so we could have multiple tweets obviously don't do that in line like that just a temporary solution now we can see these are appearing correctly with the Emojis okay so instead of this pad signup button we should probably start creating like the traditional create wizard that we see on Twitter I think this is best as a separate component just because it's like fully separate we might want to reuse it on other Pages const create post wizard we're gonna need the user's info here in order to actually render the preview for the user like ID and such cool so we need the user info here const user that'll be the user info we can return something else if we don't have that new user return I'll just return it all for now that's fine and then we now know if the user has to exist we could grab a mutation we could go make the mutation I want to do that just yet I want to get this actually rendering something decent let's put the image in all right Dot profile image URL nice alt tag is I'll just that looks fine to me we're not actually rendering this yet we need to render this on the condition of user is signed in so I can just do that now if user signed in we'll have this which is obviously way too big so let's get to some Tailwind class name equals Flex this needs class name equals Flex Flex ah this doesn't need to be a flex it seems to be a size we'll do W 48h48 rounded full Tara still way too big let's go a bit smaller with Tailwind you can usually just bump up and down until you're close to what you're looking for and then adjust accordingly we'll go way lower we'll do 12. that's too small 16 . that looks about right to me so maybe I'll go a tiny bit smaller yeah looks good to me I need to have an input here and put this kind of a placeholder type some emojis see how that looks absolutely terrible we got a lot of styling to do with inputs generally default input styles aren't great especially once you have like different default colors than you'd normally have in the browser so let's give this some new properties class name equals first we need to get rid of the background I killed the background honestly it already looks way better it's going to need some padding on the side uh where do we want to put that padding so generally I don't like using margins and if we were to add padding here it would effectively be behaving as a margin not the worst thing honestly I often put like a little div between things but for now it's easiest to just put a gap that looks like a decent space maybe a bit smaller yeah it looks good cool now we have the gap between the elements this can expand as much as it needs to and I can type should probably make this full size is this full size BG red 200 it is not full much better and then grow ta-da now it's all the right size kill that because we don't need it anymore I don't like the outline on this what's the code for that it is outlined none cool outline none and now we have a pretty standard looking input that looks just like the one on Twitter nice this all looks solid right now clicking this doesn't do anything we should make profiles work we'll add other routes in the future but I want to get this first page looking correct and we can go back in and add the rest of the functionality so now we have that we don't have a way yet to render profile images here because as I showed before in the database they don't exist the X Prisma Studio and you'll see in posts we don't have an author ID so we can't render anything yet so we need to put an author ID there again I don't want to do this all correctly just yet so what I'm going to do instead is we're going to console log user Dot I'll just console log user for now and here we should get a ton of stuff being logged has crazy things like backup codes whatever we don't need any of that what we need is the ID now we have this ID I can copy go in here paste make sure we kill the quotes around it save now we have the user IDs coming back here there's a lot of ways we can get the data from that but since we're not using a traditional relational database to have the users in the same DB we do have a relational database technically it's SQL based we could keep everything in sync using stuff like web hooks from clerk but for now it's easiest to just get the data straight from clerk I don't think we need to do that on the client side when we can do it with a single call from this error show you how we do that real quick so if we go to the place we're actually getting this data it is right here again command click to bring us the backend function we're currently immediately returning the prisma.post.find money I don't want to immediately return a promise I want to actually use that promise I'm going to make this async const posts equals await also going to make one change here limit 100 I forgot how we actually do we're gonna take again type safety is incredible it will auto or you can in this case control space and it shows you all of the unique options you have if we only want to select post by a specific person we could do where user or account is it author ID we use yeah author ID and you can put a unique ID here and now we'll only select things like that autocomplete and type safety are so much easier to work with when you're building like this so we have the posts here but we don't have the users all we have is their IDs and we need more data specifically we want to get the user profile image URL so how do we get that from here we use the clerk API I don't mean like go fetch stuff from clerk I mean we're actually going to use the Clark client it's super helpful then we need clerk clients so const users equals 08 clerk client need to import this next.js server see how convenient this is from clerk they will actually provide you with the next.js package crazy things like this to just fetch like Quirk information so clerk client dot get I think it's dot users yeah dot get user list and this can take stuff oh that's pretty convenient one of the keys the get user list call from Clark can take is user IDs which is a list of different IDs that you want to be fetching so in this case we want all of the author IDs from Posts and I can even limit this to be the same 100 so it will never be able to get Mar oh is it user ID not user IDs so close copilot there we go cool user ID is a string array this will get all of the users information for these posts and we can quickly console.log users here just to prove that we'll continue returning posts for the time being but when I go here you can see we're getting a ton of info but importantly we're getting info we don't necessarily want the user to have like password enabled totp enabled and all this other stuff we don't want this to be returned for every user for every post so we should filter this down there might be a way to do that through the core client I don't care to do that let me say we're going to do a custom function const filter user for client equals user we'll get this from Clark we just want the type so make sure that's type import you can return the ID I don't want to return all that stuff name user Dot username so we can keep calling this username just for consistency sake and we also could use the profile picture user.profile image URL again I'll try and keep the names consistent now when we pass this a user we'll get back just these three things so after we do the await here we have users we can filter dot well it's not filter.map filter used to be wrapped voice in Texas is the prettiest thing but here we go now we have filtered users with these specific fields way easier to work with and then when we want to actually return everything mapped return posts.map post and this needs to have the post and author look at that well this seems to be about it's an object now for each post we are grabbing the user that made it using the author ID this is effectively creating a relation so when we call this I can go back to our client and hover over we can just map over data but we need to grab the post off this so do that we also have this user info that we didn't before oh it's not User it's author now we have this additional field that we didn't previously have and we can do whatever we want with it super handy and we know that because it's all coming back through typescript this is part of the type definition if I was to go back here and I was to rename that from author to user I have to save before we go to type error over here because that's not named author that's now named user super super handy now we have this info we're going to want to render way more here I am certain we're going to need this somewhere else in the future so it's easier to break it onto the component notice I'm not using new files just yet don't make new files until you know something's needed somewhere else that's how you end up with a bunch of files in your code base that no one's using for anything at all keeping everything in this file for now uh we'll call it post view equals well we need the info from this post and here's where I'm going to show you guys a really fun trick in trpc rather than having to define the type for what this takes because like I could sit here and type out all of the things that a post view needs to render a post but we have the type it's whatever get all here returns so I need one post off this that seems like it's going to be obnoxious to get the type of right well we have a small trick for that in the API we have some helpers defined we want to define a post type so type post with user equals router outputs cool this is a type if I go here it comes from the actual router that we've defined you'll almost never need to be inside of this file but it is useful to know what it does it creates the actual trpc next API definition that we fetch from but we also use that definition to generate the router input and output types and since we have those helpers I can now from here select posts and get all and now this is going to be post with user post author array but we don't want that to be an array we want that to be just one of the elements from the array so we're going to do number and now that tells typescript hey we want an element from this array type and now we have here post with user has I don't know why I would have click to make that up here post with the user is just one post and author which means we can do props post keep hotkeying the wrong hotkeys my bad and now we can do whatever we want to in here if I go back I'm accidentally scrolling too far my hotkeys are all being super weird at the moment you Inc return you Inc post probably isn't the best term for this since it has like a sub post I guess I can just do props post with user and then const post Arthur equals props and now that all behaves as expected I can go back here do that rather than doing that I'll do fall post and since this has two keys like if I hover over it has author and post because I dump the prop directly we can do the same here dump the prop directly and it's missing a key and we can get that easily with key full post dot post dot ID oh not double bracket maybe yeah that was silly me sorry cool and now we will return one post for e or we'll return one post view for every post properly keyed with all of that data making it all the way through we have everything that we need up here now too super convenient one thing I did notice is that we're getting or undefined for the type here that shouldn't technically be possible there's a bunch of ways we can assert that that's not possible the easiest one though honestly exclamation point we might get an error saying forbidden non-null assertion yeah not a big deal the other thing we could do is throw if there's any that doesn't match but yeah this should be fine yeah we'll do it the right way the easiest thing here is to not immediately return this so no longer brackets there at the bottom return cons author equals if no author throw new trpc how do we do is there a message onto your VCR I don't use trpcr too much I normally just throw some lazy but TR is the correct way it has an actual error code that will throw the right status symbol and such and we need to put a message here we'll say uh author for post not found and now we know author exists I can delete that and now what we return should always have an author yep author is no longer or undefined nice and simple again typescript forcing us to actually check the things that we're doing this makes sure each post has an author and if it doesn't we throw and once we have all of the posts for each author or once we have the author for each post we return that all mapped together and now we have all the data we need to render this correctly let's actually render image source equals author dot profile image URL and we'll see we have that same fun problem from earlier Joy how do we deal with that let's get to it well obviously this needs to be Flex so uh Flex let's start cool that helped I have no idea why there's this one post at the top and then the two after that's a little sus uh we'll just keep styling this for now and figure out what's going wrong in a bit I'm going to yoink the image styles from here we'll deal with alt in a bit okay cool that fixed that but I don't know where this third post is coming from okay oh I know what it was from that was a bad State because the database yeah why would that have been in a bad state I can't sit here and honestly say why that happened sometimes if you see something weird just do a refresh I'm guessing that there were some persistent data from an incorrect state in the past hot module reloading isn't always the most reliable don't be scared to hit refresh every once in a while okay so that padding is way too big we're using for padding in here looks like we're not uh we have a padding for wrapping this section so that's probably what we should use here yep that is much more aligned we want to get that text aligned as well though so let's fix that so hear that there's a gap we did Gap three before I believe yeah that looks nice now you need to actually do something with the content span honestly this should probably be like a separate div we also put the username there and such let's do that now div I'll say class name equals Flex Flex call because it needs to be vertically stacked and we're going to have the div with the username here I'll also make this Flex this is going to have more info than just the username Flex span this will be author dot username to close the bracket there now we have that you can put the at here by putting it in the string out there I try to avoid doing too much string stuff inside of the jsx template directly so instead we'll do this in a string template I entirely missed all of that at for the at sign then dollar wrap that oh because it could be null I know what we can do we can go back to this type definition that is so convenient being able to command click and go where you want to be so up here if no author or no author dot username ta-da now if we don't have a username it will throw no I'm going to do that wait why did that not work oh that's annoying we're actually unmapping the type I know we're not or no author.username then at this point yeah that should if no author or no author.username oh yeah it's not deep checking enough typescript's not the smartest always yeah it's gonna yeah see is that correct okay then why is it lost the type there I guess authors type is an assertive but author dot username that's annoying and now yeah typescript's stupid cool we have now fixed that type error no that's not string or null I need to restart eslint yeah so this is like being done there that's now fixed author.username is valid and if we look here that looks correct the actual font color looks super off though so we'll give it text slate 400 it's a little dark we'll go with that I'll do more styling in a bit should probably put the Tweet like when it came from or what time it was posted at that is a really nice feature I like having the Little Dot honestly the easy way to get that little dot is to actually go to Twitter find a tweet that has it select it that's a funny tweet Adam now that I have the actual character we'll just do one hour ago to see how it looks looks awful because we need more spacing between these things um trying to think the easiest way to do that there's a bunch but uh I don't love any of them honestly we can include this in the next span it's gonna need to be a string template anyways we'll do that now knowing that that white space isn't being honored but uh welcome to strange CSS behaviors where that gets killed why did that not happen when I did this before Oh I think because I had a link around it before I just put a fake a tag around this without do it I'm super curious it won't good to know cool and getting white space to behave is one of the most fun challenges in all of programming building that makes it look a good bit better but the space there we'll do it nope white space never gets bigger white space never behaves so that font being bold looks weird class name equals font thin yeah let's put a gap will that work I wonder God I hate CSS we'll just put it one Gap that looks fine CSS hacks obviously you should make a better container for this I don't care to also Less in love with the Bold so I'm going to kill that yeah that looks fine to me not perfect but I've done worse for Less I would like it to actually put the date the easiest way to deal with dates that I use nowadays is day Js the reason I'm thinking of JJs is I love their relative time that's not actually how you use the relative time plugin here it is so with ajs you can install a bunch of extra things if you need them because they want to keep it small by default so npm install JJs let me go back here import DJs From the ajs import look at that ta-da and now we have DJs with relative time funny enough if you try to use DJs without doing this it's smart enough to yell at you when you actually try to use the relative time functions so if I go back down to here dayjs Dot I guess you just called ajs we'll pass this the post dot created at Dot relative or I think it's time since let me I wrote this somewhere oh from now there we go now that shows correctly 18 hours ago because I took a break and went to bed so that was actually how long ago I made that post originally super convenient all in that one line honestly this is a weird enough thing I'd probably break it up into a separate component but for now this can all live here it's easier that way you might have noticed that all of the oh what is this mad about oh because this is a type import cool now that'll stop complaining not using this anymore we can kill that so you might have noticed this yellow underline on the image tags that's because we're not using next image next image is a super nice plugin that next.js provides that combined with versel will actually optimize your images so that they load faster and in the ideal condition for every device you can also have blur hashes and other things to make the images look nicer as they fade in it's a really nice plugin so let's make the switch over switch this to image same change here this one's still upset because I don't have an ALT tag so we will add the alt tag of this one should be a little more specific since it's a specific user's profile picture so we'll say at author author.username profile picture nice descriptive alt text is missing required with property oh yeah they like to have specific width and height properties so with the [ __ ] is a safe number okay what is 3.5 Rem convert to 56 pixels that means I need to go swap it above as well why is it not mad here oh since it's going through clark.dev we have to add that to let next know hey by the way you can optimize these images it's safe trust me bro so we will hop to next config and that would go under easiest way is to look at the actual docs images remote patterns easy the domain's even easier in the domain we needed was images.clark.dev paste save and now probably have to restart next because there was a config change and finally if all goes well we should have optimized images ta-da we can do tons of fun stuff with that I want to do blur hashes because they're really nice uh yeah placeholder equals blur does it not automatically blur anymore used to create the polar placeholders that's so sad that's annoying rip whatever things load much faster now good changes we've made a lot of changes I haven't committed in a minute so I'm gonna do that before I regret it next image tag style up I think that's a fair description of what we've done since cool I was noticing how awful the loading state currently looks it also blocks on everything ideally we would have a separate loading state for when this comes in versus everything else especially since this will be blocked on database and this will be blocked on clerk which should resolve much faster so let's break these things up a little bit first I want a nicer looking loading spinner there's a lot of ways to get them since we are deep on Tailwind already I'm just going to search Tailwind loading spinner and see what comes up this looks perfect to me so we're going to yoink make a new file since it'll be a reused thing since we don't have any reused components yet I'm going to make a file for it or a folder components in here loading.tsx sport const loading spinner equals turn and it's going to be a couple instances of class select those change to class the name otherwise I should be good to go and if I go back not to server but instead to the actual page we were in index.tsx it's going to close everything else I have to worry about it as much scroll down to here where we have the loading so you have to import that just to see how it looks that looks okay kind of prefer the white one does it have I'm just seeing where the color is done it's been dark oh Phil cool then let's go back to loading and see the fill Gray oh no that's the same color was it great 200 they're using oh Gray 300. we're using slate for everything so that's gonna stick out let's change this ball from grade to slate that looks really good but it's also in like the worst possible place there's a bunch of ways to handle it honestly the one I found excessively be the easiest is to do a separate component const loading page class name y'all CSS visitors are going to hate me for this but I it's just so much easier to handle loading save this way absolute uh top zero right zero W screen H screen Flex justify Center align Center Airline middle I think and then loading spinner and this is what we're going to use instead also going to change this to is loading or true because I want it to always show cool so I missed one of my properties is it items Center yes item Center cool so I'm middle is not necessary then it's also way too small so let's fix that we should have a way to increase the size okay we have h8w8 here which are not ideal also a margin why do we have margin right two I do with equals 40 height equals 40. hey cool that works so we're going to do is have number or size we passed here it's an optional number now you can pass a size or it will be 40 by default oh that needs to be under props duh cool and now we have the option to pass the size here so we'll pass this a size we'll give it 60 because it's like the big one nice prop size or 40 is probably too big for a default we'll do 16 because we're going to need this in a lot of other places right now we're only showing that if user or data are loading I'd prefer we only show this in the case of user actually user is going to return fast enough we should probably not show this for that case we should just show nothing until we have that data so let's do that for now also going to bind these both the names posts loaded so if no user loaded and no posts loaded return I'll do an empty div I'll put a Comics is a weird Behavior return empty div if both aren't loaded since user tends to load faster cool and we don't need to return loading page or something went wrong honestly I kind of want to contain that feed in a separate component now because we're combining data flows here the only issue is we won't start fetching this data so how do we make sure the data is being fetched in both places but also break that off and call the same hook twice so let's do that const posts uh or we'll call it feed equals doesn't even take any data in we can join to the sky paste it here we can um hey we're checking for users State before that's fine we'll get there in a sec I want to link this first yoink return yoink data oh yeah if it's not defined then we need to type loading States so if data throw new error I'll just return it in line cool why would data be never then oh it's posts loading that was done with me posts loading because it's is loaded for the user one so clerks and react queries conditions are inverted which is a very annoying thing to deal with so the only reason we're using this here even though we're not using the data is to make sure this fetch is early the other thing we can do is that start fetching ASAP cool because with react query you only have to fetch data once and as long as the things you're fetching with are the same it can use the cached data so now we can use that cache data we can start fetching early and have it ASAP do you need to actually render that feed though uh full Porter slate feed and here we're not handling okay so let's say if not user loaded or I think that the is signed in was coming from here before we'll delete those I mean we need that and that should be a much better state in the browser yeah you'll see that this comes in first then we have the loading State for the rest of the page and then the rest comes in right after don't need that anymore the other thing we can do to like more properly simulate this is always return here so you guys can see this is now the behavior when the rest of the page hasn't loaded just yet which is a much better Behavior good stuff cool looking great so far let's get that committed loading States one more thing because it's bugging me I want to make the text bigger on the posts so let's do that an actual post here class name equals text Excel yeah looks a good bit better how do I have this set there that's 2XL might be better for this yeah that looks a lot better we can rock with this cool time to actually make posting work yeah time to make posting work commit that easiest way to get started with something like this command click so the one problem we're going to have is actually knowing who you are as a user and if you have permission right now we don't have anything set up around permissions with trpc because we didn't use create next off thankfully it's a somewhat easy problem to fix we just have to dive in and fix it so what what I like to do is attach the auth state to the context inside of each query so right now we have access to Prisma directly in CTX we don't have anything else if I go to definition here oh it's going to go through trpc source so this is one of the few times where we actually open up not root but the trpc file you almost never need to edit this file so we see here you want to create you want to modify the request context or create a new middleware or type of procedure and those are actually the two things we want to do here so let's Dive Right In so the first thing we need is the session we don't need this create in our trpc context thing because this is useful for like testing stuff so honestly I'm just gonna link skip this helper probably useful if you're testing I'm not writing unit tests for this and even if I was I wouldn't be testing at that boundary kill that as well but in here there's data that we don't have that I need how do I actually get the user info at this point well within options we actually have a request so I'm no longer going to underscore Escape that because we do actually want to use this const request equals Ops this is an xjs request from an API which you can actually pass to stuff and clerk like const user equals get user so I get off because technically we're not going to fetch the user from Clark each time since clerk is using uh what's it called uh jwts it's able to verify on your server whether or not this user is authenticated or not using the signature of the JWT allowing them to skip a callback to their server just to be sure the user is authenticated so this lets us know for sure this is this user and give us a little bit of info about them specifically signed in are signed out off object which has uh the behavior of if they are signed in or not I could just throw this directly on honestly it's probably the easiest thing and now when we call this in other places it will all behave as expected super handy the magic here is now when I use it in a procedure so we won't always necessarily have off but we can enforce or but we can make a procedure that enforces that we do the easiest way to do that is to extend the public procedure with a new middleware it's not the same as a middleware in next.js where it runs on an edge function it's just a process that runs before your main request processing super helpful for things like attaching off and making sure users actually authenticated since we're already attaching the auth earlier we can verify here very simply const and force user is offed we're going to continue using the T helper from trpc middleware oh look at all that good thing there's so many people writing code like this so it's that easy to just autocomplete our way through we do need to attach the session here uh let's dot error yeah CTX session ctx.session and this should enforce that session exists sadly we're using signed in or signed out let's clean that up a little bit actually because I don't want this to be assigned in or signed out state user Dot oh this is actually we'll call this sash for now const user equals dot user and user can be null or undefined that seems more reliable cool we'll call that user instead of session because that's what that is should we call it current user yeah better naming and of course right as I'm starting to make progress with this my laundry goes off but more importantly now this will always return with current user because if it doesn't have a current User it's going to throw an authentication error this also should be a trpc error and the trpc error will have codes import trpc error in this file I guess so code ta-da not actually use it all I have to do is const private or export cause private procedure equals that is not how that works we'll just extend that not user use enforce user is auth and now we have a private procedure that one used will always have an authentication object and now we can make a new protected procedure if I go in here we can make a new key oh look at that why don't this be public we actually want this to be a private procedure and this will have context which actually has more info than previously so it needs to be wrapped we're actually doing anything yet you should probably do that wrong buttons const user or author ID equals CTX dot current user dot ID you'll notice we have all of that info from the current user because we've asserted that they exist if I go back to the procedure before that's a public procedure or even if I was to just change this from private to public you'll see we don't actually know current user exists because it could be null or undefined but when we switch it to private we know it has to exist super super convenient this makes life way easier and you have this procedure that it's guaranteeing that the user is again authenticated what does this upset by oh there's no weight so it's not technically async yet so we can deal with that this needs data it's currently upset because we don't have content I'm assuming yes content is declared but not defined but where do we get that from we don't have an input again trpc makes these things Magic we do need to tell it what input we want here though in this case we should use one of the core technologies that makes stuff like trp so powerful something we haven't touched on just yet we're going to be using a lot from here on out Zod Zod is a validator often used for forms I'll look it up quick Zod was invented originally to validate that a given piece of data does match a specific shape so if you want to make sure that you got an object where you have a user ID that's a string that's a certain length and all these other types of properties solid makes it very easy to do that there's a ton of cool things in here one of which we got added recently that is very helpful I wonder is it included in the docs it is z.string.moji to make sure that a string is a valid Emoji so in this case we want to do a z dot object that has content which is z dot string Dot emoji dot min one uh we did two yeah we did 280 care in the database so that works for me and now we know that content is a string that is one character or that is more than one character or one or more characters and two 880 or less characters that's all you have to do to guarantee that but how do we use it input and now it's right there content string and we know that exists again because if it doesn't trpc will throw the error for us and a user can't actually call this without passing this validation so I have a Content safely there and now oh I forgot to put uh input dot content and that is totally safe ta-da Magic if we want we can return the post so turn post and that's all it is it's that simple to make some or in API endpoint that does something that's important if I go back to the client we can actually make this work relatively quickly I will go to create post wizard const mutate equals API Dot that's pretty handy mutate will have an input oh we're not actually using it yet but if I call mutate without putting anything in it it should get pretty mad at me for that yeah cool expects one or two arguments but it got zero that's what I expect we can fix that pretty easily so if I we go down to here uh sure I don't feel like managing the state here it is much easier to do this all outside of react think unkey press is bad on key down cool now we have the on key down yeah I don't want to do this this way I want to do this the the boring old school react way so we'll have const input set input sure values input uh this needs to be type lots of complaining no oh you state isn't imported there we go also doesn't need to be typed because we're passing in a string so the type is node again you very rarely need to declare types only for inputs here that also is behaving correctly and we can just add a dumb submit button for now button on click equals there again copilot doing its thing that all looks correct to me if we go back here it looks ugly but if I post this click post nothing happened but if I refresh nothing actually happened interesting do we get an error unauthorized maybe I didn't set something up correctly on my context Setter there then if no context current user throw unauthorized user oh that should have worked console.log sesh I'll just log both of those real quick okay oh because user doesn't that's so annoying it doesn't actually have user it just has user ID and if session exists or not because it doesn't have the info that we need well the type that that's giving is incorrect then because we're getting back user from get off even though user's undefined just some bad Types on Clerk's part I will bug them about fixing that in the future if this isn't going to come through with that info it shouldn't mislead in such a way so now that we know all we're going to get back is user ID to know that it's assigned in person we'll do that cool so let's grab the user ID that is what I was getting here right yep cool and user ID is what we're going to use we will also use that here now if we actually go back to the code where we're doing this it's going to be type erroring of course because we don't have current user.id we have user ID which we know exists because if it didn't it would have thrown way ahead of time and now I did this all correctly back to Chrome try once more again we're not seeing anything happen but this time if I refresh we'll have the new Post in here ta-da immediately noticing something specifically it's out of order and also we don't need to be doubling up posts anymore so it's good to feed stop doing this because that's awkward so we don't need the question mark anymore because we know it exists and if we go to we're actually fetching the data it was in the wrong order the best way to fix that would be in here is that not yeah order by that's what I thought it was I'm forgetting the specific Syntax for this in Prisma I could just look it up but uh yeah let's just do it Prisma order by filtering and sorting cool sort not order oh yeah cool it's just a thing underneath order bye created at descending cool so now it will be ordered by created out stuff in reverse order if we go back here refresh seconds to load But Here we are now it's in the correct order where this was two minutes ago this was 19 hours ago we can now post with different emojis and it works you'll notice though it didn't automatically load and set stuff it's not even actually clearing this input there are some quick ways to fix all of that I will show you the easiest is to on the client update once the post has happened so the first thing we want to do is deal with the loading state so if a mutation is happening we want to render things a little different so in here we'll do mutate is loading it'll say is posting so that this has a better name we'll go in here say disabled equals is posting and I'm also going to append a class name and we don't need to do that just yet ideally we would dim this we'll get there in a bit but for now we just want to make sure the input is disabled Wally post is occurring and we want to make sure this behaves a certain way on success so on success we want to do something in this case we want to set the input to empty but we also want to update the existing post on the screen the easiest way to do that is to grab the context of the whole trpc like cache through the API context call so say CTX equals API Dot use context and in here CTX Dot and validate forgot the is there a better way to do this yes I see it got it no oh dot yeah invalidate no let's upset what's this upset about oh because it's a promise not I get why it's upset but we don't actually care because in here this this doesn't care about what's going on with the promise so if I put a void in front that tells typescript hey we don't care this is a thing we want to have happen in the background automatically so now when I make a new Emoji post and put a skull post disappears in the post appears directly underneath that's all the core functionality complete for the main app and its functions truly magical git commit uh I wanna do my usual piece by piece I saw a console log in there I don't necessarily want so I'll go delete that in a sec working posts now we have posting actually working pretty cool but I want to make sure users don't spam posts that would be really bad and the easiest way to limit the amount of posts a user can make is to use a rate limiter a rate limiter is a pretty common pattern it's used to enforce this thing can only be hit this many times usually use like the user ID or their IP address to enforce that I'm going to show you just how easy it is to protect based on their user ID easiest way to use this or the easiest way to set up a rate limiter is using upstash they are a fantastic service for everything from redis to Kafka to cron jobs to event management all the annoying things to do in serverless are handled pretty well by them and one of the things they have is an awesome package for rate limiting so there's a little console button here you can't see I can see it just fine though signing with GitHub again if a server doesn't have sign with GitHub can we really trust it we're gonna create a new database for this I'll name this uh Sharp rate limiter we specifically want it to be Regional not Global because if it's Global it might not distribute correctly if people are spamming and Regional just guarantees this thing's going to exist in one place it might block for a little bit since we're in US West 2 probably want to put this there it's now being prepared for us while it's being prepared for us I'm going to search upstash rate limiter let's talk about this package on Twitter a bunch already you might have seen it before but it is so helpful all you have to do to use it is install it should probably be installed in Dev only if it doesn't matter too much I also need to install upstash redis there's a lot of places you can do the rate limiting at I'm gonna do it just straight up inside of the trpc post function because that's the easiest place we're going to constraint limit is new rate limit write a stuff from environment only problem here is the from environment expects it even says here when we hover over I've got obsessed with type definitions are incredible this tries to load up stash rest URL and upstash redis rest token from your environment using process.environment we don't have either of those but if we go back here show you if we scroll they have all these different ways to hit it but if we go to here is it the rest URL that they want I don't think yeah it is the rest URL and rest token so I can click copy here go back to my DOT EnV paste this all and I can also more importantly go over to versel and paste it all in there as well chirp settings environment variables paste check check save and with just that one change we now have the ability to rate limit using this rate limiter currently going to set a rate limit of 10 requests per 10 seconds I want to be a little more strict than that let's do three per minute so instead of 60 so s we'll do or 10s we'll do 1M change that to three requests per one minute and if I scroll down to the mutation we have the author ID cost limited equals a weight I think this actually returns success so when we call rate limiter.limit this is going to add a tick in their little Checker and it will tell us hey are we able to do this or not also just realized we lost our typescript server at some point during these changes cool that fixed it fast success and from here if not success throw new trpc error is there a rate limit code there is not too many requests same difference and now if a user tries to post too often this will immediately throw and prevent them from doing that it is that easy to add a rate limiter to anybody saying that this stack isn't production ready or can't scale here you go that's all it takes to on a serverless environment without impacting your database or external anything we now guarantee that this can't be taken down with just straight up spam it's so easy to make changes like this and you can't even get this far if you're not authorized you have to have a valid cookie to get this far and then as you spam you'll get kicked out we can limit by IP address we can limit by so many other things but with this one simple change using upstash it's that easy to keep users from spamming us obviously we need to come with that now we're production safe so cool what else do we have to do well it would be nice to show on the client if an error occurs like if we try to post and we can't because there isn't an emoji like if we type normal text and then we try to post it's going to fail but we're not seeing that anywhere I would like it to be a toast honestly that tends to be the easiest thing so let's do react toast react hot toast is probably my favorite toast solution nowadays there's a bunch of them in react and they're all pretty good and here are the instructions on how to set things up we're gonna go npm install react hot toast while it's installing we're gonna go put that somewhere again they said put it somewhere where it's pretty high up nothing higher up the map foreign place to toast is on error so if I go here on error you can import toast dot error failed to post at least try again later it's probably not the best error but we'll show you what that looks like really quick you can go here post failed to post please try again later again it's that easy to add some of these things I would like for the toast to appear at the bottom rather than the top I think you can customize that in toaster yep look at that we want bottom center so if we go back to app TSX bottom center and now when we get an error it won't get in our way because it'll appear on the bottom instead pretty cool huh like it's not easy to do these types of things and react in this stack obviously that's just a traditional react thing but here we have the error message coming all the way through we're not getting the error message just yet the const error message equals e dot data Dot Zod error Dot is it Fielder yeah field errors.message and this will either be a string array or undefined so if type undefined all right okay so we know our message is a string array so okay cool doesn't matter what the non null assertion I do if our message and error message zero and then not do that cool I can do that else and let's see what this looks like now oh look at that we didn't actually get the message through that's annoying uh can I just console logs out error I don't care it's utter object field errors content okay that's annoying so the types just wrong solder dot field errors Dot content there that's the error message we're looking for haha now that is the correct message that is what we were looking for if we want to give a custom message again Zod makes this trivial if we go to the actual place where this is defined create in here in emoji we can put a custom error of uh only emojis are allowed now we try to post without an emoji we'll get a different message coming from the server so this is a message the server created in the validation process that it gets displayed on the client super cool truly magical and if I clear that and post something else like uh arrow pointing I hit post twice because post isn't disabled when a loading state is occurring so we should do that quick too is loading his posting we can also put a loading state in here pretty trivially that would look nice let's do that we'll say value just not or input does not equal empty string and we'll render button and we can say is posting cool that loading spinner doesn't appear in the right spot at all that's fine we can make this bigger first size equals 20. div I also should probably just hide the button and not is posting let me do disabled state just become this instead class name equals Flex Flex call it doesn't need to be Flex call justify Center and I'll just say true for now yeah it looks right that looks super nice skull post loading State appears ta-da look at that good ux only thing left here is submit on enterpress which is not to do on key down and wrap this with the wrong bracket type I bet this will even autocomplete look at that fantastic now put a new emoji press enter loading state and the post has been made magical I don't feel like going through all those changes so we're just going to commit it all at once uh posting loading States plus toast if I was doing this for real rather than waiting for the server to block on those validations I would much prefer to do it on the client side using something like Zod and react hook form so we could use the same validator to check on client hey is this valid or not also using reactive form to manage the input state is much better than doing the chaos I was doing here where it's actually in react State this is how you get that weird sticky key Behavior where you're typing and then the keys don't appear and then they all appear at once it's because we're actually re-rendering all of this on every key press far from ideal just wanted to get this working fast I would absolutely gut that for reactor form if this was for real though cool big things that are left are the profile of you and post view I would like for clicking the profile picture or the title here to bring me to their profile but I would also like for clicking this or anywhere else in the post to bring me to this post both of these will be separate pages with separate views the easiest way to get started when you want to make a new route in next.js Pages directory usually take the one you want copy paste it so we need two new routes that are going to have a specific matching syntax so it'd be nice to have like the at slash syntax like we're used to on Twitter for the profile but we'll start with the post which we'll need to indicate separately so we'll do Post index.tsx in here but this needs to be marked somehow so we will do ID dot TSX and now we will be getting now when we use the next JS router ID will be a thing we have access to very helpful and for the actual route outside here the copy paste again we're going to rename this one to be slug dot TSX so this will catch on everything that isn't in this folder so it won't catch on post it won't catch on API it won't catch on any new files we make in here but it will catch for everything else in this case at slash stuff so this page will catch when we do localhost 3000 at Theo we'll do at t3.gg and it's going to catch here which right now the page is the exact same but if I was to replace what it renders by scrolling down here in order to change this to profile view then we'll see this is a different route if we go back here it will still render normally first thing we should do is actually get links working to these posts or to these new pages so honestly I'm just going to delete everything else in here that cleared out a lot fast do we even need the user info really we don't cool nice simple rename this to profile page honestly let's do the same for in here post single post page now we have these two separate files that have error that represent different routes and close all the things we're not using at the moment now we need to go to our post view which we have here and we want to make these links so we're going to use the next JS link component the reason I'm using that instead of just an a tag is when you use the link component the routing will actually occur on this device rather than occur words are hard the reason we're using the link component is that it will prevent the routing from triggering a full browser refresh so we don't have to wait for the server to get data then there's a whole new HTML page instead it can happen in the single page app style where you click a button it can immediately start loading the next page and next is smart enough to start pre-fetching things in the background too it's really powerful pattern change the href to be slash at dollar sign author dot username nice now that will work and I would like for this or clicking the from now part here to I know because the Dot's gonna be linked to whatever if the dot links to the post that makes intuitive sense because the whole thing's going to link to the Post that Auto close that I also want to turn off auto closing tags it's more annoying than helpful and this needs to be slash post slash and then the post ID post dot ID there we go now post dot ID matches this and author username is that you can click either it will load profile view or we click here it's still going to say profile view because I forgot to rename it we will rename this quickly to say post view there and now we have routing working pretty cool I might not be the biggest fan of file based writing but it is super powerful to be able to Define some files and have a whole linked system like that working correctly let's quickly add that all new files also quick note on git add P if you're still using that it does not trigger a new file so you'll have to manually add new files that's why I just did the dash a there new files for new routes here's where we get into some of the fun magical things that even prpc diehards might not be familiar with I actually learned about these things when I was working on this project and they're super cool especially if you want to not necessarily do the full SSR pass on every page but you do want to be able to embed metadata let's take a look so right here you can see we have this title create T3 app on every page that's not what we want to name this we'll probably rename the home page to Sharp let's head down here cool Sharp uh thinking honestly I think a bird would be a funnier and no because we have uh the favicon which we'll deal with later this could be the default on every page let's do that so we're going to throw that in app when you throw head info in app it will be the default but then when you put it in other files after that will override so we no longer need that guy hop in here no longer need the icon I need to rename this to be post we'll just leave the title here for post Title Here a couple of these profile I would like for this to be the actual profile for this user though so how do we do that well the first thing we should do is Define a trpc query but we don't really have a place for it yet so let's start by creating a new sub router that makes more sense for it we'll create a file we'll name this one profile TS I'm going to be lazy and go yank that we'll rename this from post router to profile or router we will kill everything inside of it it's going to need any of that anymore I don't think we need the rate limiter either for this at least oh I don't need a lot of stuff that's in here silly procedure so we'll start from here pretty simple empty router wow like almost not as necessary so first we need to have a way to get the user now we're going to be using the username to get it so we'll name this get user by username it's going to be a public procedure dot input Z dot object username Z dot string dot query I will admit that this stuff getting in the way as your typing is really knowing it's nowhere near as bad on like normal resolutions but when you're on this like I'm scaling 7 to 720p when I stream and record which makes it look a little rougher than it is so hopefully that won't be quite as rough in your environment I have one too many friends here and now all I have to do is get the data the easiest way to get what I'm looking for here oh the clerk client call there is incorrect it's going to be get user and use a get user needs to be user ID so get user list and this used to be an array the easiest solution there is to make it one if no user these are not found I should uh see I'll do that so we're only grabbing this one user from in there because we only want the first one theoretically if username finds multiple we want one multiple we just want the first one so yeah that should return the user we don't even need context because we're not doing anything with Prisma here so if we want to go use that in here const and data equals API Dot I forgot to actually add the router so if we again go back to here we're exporting profile router we go to the root import done now your profile Dot get user by username dot use Query if we need to have a username here we'll just hard code mine for now okay if is loading if node data return div 404 and then we know we have data we'll just render oh no this is gonna be another one of those where we have way too much data on the client we'll take a quick look it is almost certainly add data if we go take a look at this quick click here yep that's way more data than we want so we have to do the filter again time to break that filter function out since we're using it in more than one place I'm going to make a folder in server helpers and go back to posts we're going to oink this guy any oink new file and I made the helpers filter user for clients.ts export and when we move this out we just did a small subtle but one of my favorite things that happens with trpc if you scroll through this file you'll actually notice there are no type definitions in this file at all because all input is done through validators like Z dot object V Dot string this isn't a type definition this is a validator a type definition is inferred from the validator but this is not a type definition then when you actually return something this isn't the type of definition either this is inferred from the return all of trpc is built through inference in strict validation which allows you to have guaranteed inputs and outputs always of the correct shape even here if you wanted to make sure the shape of the output was always correct you can actually do an output validator that will trim off data that you don't necessarily want so I could do dot output I'm going to do it earlier dot output input and output validator there super cool pattern I haven't started using it just yet because usually I prefer to filter in line here so filtered or filter user for client user there we go now if I refresh this you'll see we get way less data back we're just getting ID profile image and username and that's all we really want here so now that we have all of that info we can do things with it I'm rendering the data user but one thing you might have noticed is when I refresh this it takes a sec to load which means we're not actually getting the updated head and metadata here I don't want this to wait I want to have this data immediately your intuition might be get server-side props but the problem with good server-side props is I will lose all of the typing of that data because if I export const get server side props this function is going to run on every request to this page once this is deployed on versal which sounds great in principle but it's going to block every request it's not going to have cache Pages it's going to take quite a bit of time to spin up a lot of the time and actually getting the types from here to my client sucks and most importantly it's not actually using trpc to load any of the data at all so can we get around all this this is where that fun trpc thing I was talking about earlier comes from we're going to use the SSG helper the SSG helper is a super cool feature where we can pre-hydrate some data ahead of time so instead of get server-side props we're going to do get static props get using get static profits doesn't mean that this can't re-run it just means that it will be treated mostly as a static asset and we can trigger revalidation when and how we choose here we first have to create the SSG helper so I'm just going to copy and paste this specifically here paste the context uh how do I want to do our context the only real thing we need is Prisma so I need to grab the app router kind of just going to type error because it's missing things yeah we need Prisma import not import cool this is also going to be missing user which we could fetch here we don't really need it right now so I'm just going to use our null as user ID nice and I need to import super Json there we go now we have this little SSG helper so what's the point of this guy well it offers us some pretty cool superpowers I also forgot to uh give this a type it doesn't really need it what am I using that for I'm not cool we don't even need the context oh wait no yeah I do I need this for the params the easiest way to type this is to put the type on the actual variable definition for the function get static props is a type that next provides for us and here we're going to see we're getting an error because we are not actually returning a result if I go down here we can return props and right now we're not returning anything but it will stop complaining at the very least we need to actually do the fetching here's where the magic of or here's where things get really magical with the SSG helper first we need the slug const slug equals param or context Dot params.slug here we can throw a slug should probably do something different for that like return you to a different page or whatever not the best behavior again just making this nice and easy now we need to actually use the slug this is the same slide we're going to use to do fetching on the client side so we should make this pattern really consistent what's this Arrow no oh wait oh cool we're going to fix that anyways we need to use the SSG helper SSG Dot profile dot getuser by username that's pretty cool oh you don't want to do college we want to call it prefetch prefetch is a really cool helper that lets us fetch this data ahead of time and then hydrate it through server-side props we're going to await so that it's actually there and now when we're in here we need to dehydrate it if we look in the example here you give trpc State SSG dot dehydrate and what this does is it takes all the things we fetched it puts it in a shape that can be parsed through next.js server-side props in this case static props and then on the app side since we're wrapping with trpc we'll actually hydrate all that data through react query to put in simpler terms this means the data is there when the page loaded or loads and this is loading state will never be hit so if I was to put a log in here if is loading console.log is loading that won't ever actually get hit because the inputs are the same oh we haven't given it static paths this is one other thing you need to know with get static props you need to tell next which paths are valid I don't really care here because normally this is used to generate those paths on the server you could give it extra syntax before I do anything else this needs to return something with paths which is an array and then a fallback Behavior we'll say fallback is blocking because we want to make sure we actually generate them in paths I could put the array with all the strings here like if I said at t3.gg then when this gets built it's going to generate the page for my user but I don't care if it generates them ahead of time it can generate them on load so we're going to do that instead what is this oh it's async does not need to be async cool now we have all those paths and you'll see here is loading still being hit because the slug is still including the at so that isn't actually fetching here so we should remove that from the slug so const safe slug or I should say username equals slug dot replace at with empty string and now loading state gets hit because this is generated ahead of time I know it's a small thing but it is actually super cool and since this will only ever change or at least the core data here will only ever change if the username changes which would be a different page anyways or the user's profile picture changes which we can trigger as a server-side update this will never change it's a super safe way to have statically generated pages with correct metadata because now we can use that data in here we're currently fetching is loading that way but I can give props a type here too uh type of or infer and we should Define the props type so type page props equals infer get static props type and now this is the page props the only reason I'm doing this is I specifically really I forgot how awful the types are oh because we don't have something custom in here I want the slug well I guess I want the username so now that I've put that there this should know should doesn't no that I'm returning username I'm really annoyed that these helpers don't work well I don't care because we know what the actual type is here we have a username on this because we return that there and if I console log username we should see that up here t3.gg awesome the props coming through correctly and instead of querying that I can use the username and the query directly so much simpler there's now no more loading State there is just the theoretical no data state delete that as well and now via just a little bit of Magic oh these are just types nice aren't those weren't really helpful cool so we have all those types we have all of this returning correctly and overall a much easier way to get data into our client it's super powerful should probably put the actual profile name I guess they use any might be formatted differently so we can do it that way let's start rendering so we have a lot of good data here I would like to continue rendering with the same container that we used on the main page here I think it's time to break that out as a component the yeah all of this and I will make a new component we'll call it yeah layout.tsx not the most clear name especially with what's about to change and react but uh or in next but new tutorials will come out as these patterns change export const page layout uh I'm just going to be lazy and continue oh no because FC doesn't have children anymore equals props props with children I think I cannot pass that right nice now we have properly typed children of any form at all type yoink you delete we might need to include yeah I broke that oops uh It's like because it seems to be it's on div yeah it is Yep this was the one I did not need to put there and we'll delete that one extra layer and uh yeah there we go the side border isn't working because that container is only screen height I believe uh hmm foreign I should probably set a scroll here overflow y scroll there we go I'm also going to turn off page scroll because it's just a thing that annoys me uh only certain page like document Pages like blogs and such should probably have scroll on but uh I don't like it uh disable page scroll CSS disable its over scroll aha and if I did that then it should no longer yep now I can't over scroll it just stops when you try that is how I prefer that much better cool so now we have a decent layout and I can Oink said decent layout for other places like in here not in here in here now we have that same layout with the sidebars well the side uh borders super nice we should actually make this look pretty I'm thinking profile picture here the fancy half cut off like background color username and big letters and then the bar and all the content underneath okay we'll start with the main content uh image I will make this a separate div first you can throw the username inside of it last name uh border be border slate 300 we've been doing that's been 400 for sure nice now I have the pattern or the color underneath we'll do just BG slate 600 for the background color of that for now actually now I'm going to want that background color to be aligned differently because I of how I want the profile picture to fit in here wait now I can do that with a hack cool image source equals something that would autocomplete it didn't data dot profile image URL also should be image tag because next alt equals data dot usernames profile pic cool should probably enforce that username exists uh too Lacy do that need to give this a width and height width equals 48 height equals 48 cool uh here's where we get to do some fun stuff class name equals we'll do negative that's going to be too low um B that's not going to be enough we'll do eight now we're talking I do need to give this a height though h48 let's see how that looks actually looks about right uh I'm Gonna Bump this to 64. uh also let's let's have some fun with positioning so what I want here is like the profile picture there half over half under just like on Twitter t3.gg you'll see it's like half over half under and I just want to do that with some CSS hacks it shouldn't be too hard uh we'll do relative for positioning there because if we make that relative then we can make this absolute bottom zero left zero ml4 fantastic make this a little bigger uh V8 is negative 32 uh half of 96 is do I want to do a literal for this so I can update it in one place and have everything adjust correctly no what's half of 96 that would be 48 right yeah cool let's uh we want 48 that looks pretty much perfect we also want rounded full border I'm gonna do border two and border black I want that thicker I actually want this larger I was right and half of 128 is 64. which I'm all sure there's a built-in value for that looks fantastic honestly does Twitter not have a white line for the spacing it makes sense if it did it yeah it doesn't the white lines are only on the sides right yeah they did not do one there actually looks nice without it so we will kill that border bottom all right if we wanted to have a background image we would set it there but we don't so yeah it looks pretty good to me or I'd actually put the oh all of the info goes underneath convenient I'm going to leave that open and close everything else quick because I don't need any of that at the moment cool you want the name here I'm not going to actually do the name because we just have username so we could fetch name from what we're getting but we don't need it uh cool is that as being here we'll put that there see where it goes is trying the other thing I could do is put this background and extend it under but that feels like more of a hack what I should honestly do is put a spacer here that covers the rest just so I have to worry about it it's the easiest thing name equals should this be a variable now yeah this should be a variable uh consp Pro pick size equals one two eight well you know because then it can't be parsed [ __ ] [ __ ] nope telling problems that's fine I could just do it with inline CSS but yeah nobody likes that I'll get roasted way too hard if I do that okay so class name is uh h 64 PX perfect so now this is serving as like a hidden spacer to give us the right amount of space before doing other things this is going to be the container with all of the profile information so uh I don't apply the font size at this level I do want to pad or apply padding though we'll do p two to start I think P4 is what we've been going with though uh yeah it looks about right actually honestly because that's all the other info we're putting there I'm pretty okay with stopping at that just make it like look a little better font wise shouldn't be too hard uh cool text 2XL again I like using string templates instead of doing a bunch of weird hacky stuff in jsx it's going to complain that username might not exist but it does I'll fix that later cool at t3.gg let's bold to that cool that looks great and I'm going to do one more div border B border slate 400 W fault fantastic and now we can immediately start loading the feed it might make that top chunk a little smaller because it is pretty massive Let's Do height 36. yeah I think this looks fantastic Ally since it's statically generated so like the image will be there I could do a BG black for this too so it doesn't flicker in quite as badly yeah I'm gonna commit this because this is a ton of progress status get add profile page with SSG helper since the posts can change more often I don't want to include those in the SSG helper I want those to load on the client side have everything else cached and have that just load in automatically the first thing we're going to need in order to do that is an actual API endpoint that gives us that data so I'm going to command click here we don't actually want it in this router we're going to want this in the posts router because we are fetching posts we could get profile all posts from profile but where you put these things arbitrary I just liked this breakup or post is for post things and profiles for profile things we can get posts by a specific user ID so that's what I was thinking we do here get posts by user ID is a public procedure dot input Z dot object which has a user idz.string.query async yeah we don't even do that part we can just do CTX input and immediately ctx.prisma Dot post dot find many where author IDs input.user ID thank you Copilot just saved formatted automatically let's actually use that if I go back over to slug page again just going to close all the others because I am running unlimited screen real estate now I'm going to make a separate component up here const profile feed equals I was going to need a user ID props user ID is a string const data equals posts dot get posts by user ID dot use Query and user ID perhaps that user ID nice you should have it is loading for this one as well if is loading turn loading page if no data or data.length equals zero return user has not posted and then we can return div class name equals Flex Flex column and here's what we're going to need to break out that post view into its own component because I don't want to rewrite it if we sneak over to the main page again in here I have post View as well as post with user the type so we're going to yoink both of these cut go to components postview.tsx paste how to complain about a bunch of things that we don't have here honestly what I'll often do when I'm Roofing something this big so go to the page it came from I'll just take all the typedefs not all type that's all the Imports wrong word I'll take all the Imports paste them delete all the ones we're not using usually from bottom up because that doesn't have a lag when you make changes export that and we're done now I go back in here I import post view on the page now that simplifies things here a lot and then here I can data.map post View post equals post we're not going to have the user info I also need to get a key quick so key equals post dot ID but we're not going to have the author information here because I'm not getting that off this there is a billion ways we could get this Honestly though the right way because theoretically in the future we will be interacting with other users info on this page yeah yeah since theoretically if we pass that data from the client side we could override things incorrectly and if we intermingle where we're getting posts from we wouldn't want to create that possibility so I'm going to go out of my way to make sure whenever we return from the back end we are returning with the posts user IDs attached going to make a little abstraction for this of const add users your data to posts share and we'll have post be a type that comes from Prisma it's a type and we'll make that clear I'm going to yoink everything I'm doing in this function of the create in here I did that not work oh it's because that's sorry it's getting confused why why that wasn't processing put in here I can just return I don't need any of that anymore wow that's so much simpler now that we've broken that up in the console log either I want to make sure that that didn't break in here quick I think everyone promise that doesn't care yeah it doesn't nice cool now we've handled that I can go back in here uh US Post Yeah that's where I want to be posts second dot then add you the date of the posts and now that's the same format as it was before full post and we have everything we need and I can render this here I know I don't want to for anything I just want to pass this user ID user ID equals data Dot ID imported oh did I not bring the from now over oh yeah I did it get my files back open that's under you know that would be in post view yeah I'm silly again the DJs thing is a little weird because you have to extend it like internally it also means I don't need any of that here I need that here though there we go isn't that pretty cool working profile view with all the content rendering loading State all handled totally simple understandable code easy to maintain and modify good loading States everything's dynamic pretty cool all right let's commit all that render posts on profile page and we should also quickly make sure this is all working in production because again I've just been blindly pushing and one of the coolest things about this stack and the way you've been building is that it's continually deploying because of versaille see all these check marks which suggest that we haven't had any issues so far if I go here click details we can see the deploy as it's happening make sure that there aren't any issues nice and if we click the link everything loads in as expected I can go to a specific profile by clicking the profile picture everything's here I can refresh and that page content is always here now if I want to sign in I click sign in continue with GitHub and I'm signed in all in production pretty dang cool if I do say so myself next is the post view which could be very similar what we do with the profile view but we want to render stuff specific to that one post that we clicked on this assuming so many changes here I'm just going to copy paste the file again copy slug paste this into here but this time we want to get a specific posts data we able to delete most of the page layout stuff I also don't want to have to redefine this proxy helper each time so I'm going to link all of that out to a server helper server helper SSG helper Dot yes cool it's gonna again yoink yoink instead of guess I props uh generate SSG helper don't need all that anymore I can delete all that in favor of this delete all that move these Imports where they belong all the way back up at the top here grab this file override this once more now let's get to work need to rename profile page to post or single post page leave most of this the same we do need to get or make an endpoint to just get one post hop into posts we're going to need the same user data when we do that so press page up accidentally get post by ID public or since it's already named post like the router is I don't think I need to do Post without get post by ID so get by ID public procedure input is z dot object it's here I'm going to do the same thing we did earlier CTX dot Prisma dot find or post dot oh look at that oh Auto completing too many those but I do want to get the additional stuff here so dot then it's not that filter it's uh add user data oh I would just want to do that for this one okay I guess this one's more complex so it needs to be a function that's fine that's uh post equals await and then if no posts throw new trpc error code not found oh since add user data to posts it's mapped you can do it that way typescript died times group died again that's a funny one cool that's what all network is expected and if I go back to here instead of slug we want this to be ID we don't need to do any Replacements here and we're not fetching by profile anymore we're fetching dot by posts and just like when we call the API we can autocomplete our way here get by ID this needs to have all right I need to dot prefetch again I'll pass the ID down here we don't have username anymore we have ID we don't have this query anymore it's posts.getbyid and this gets passed ID I'm going to format this nice and fancy because why not we'll do data dot post dot content data Dot author.username that like that Isn't that cool we can delete pretty much everything here which means we can delete everything up here because we just want one post here and that's it delete all that stuff it's not being used and if this was done correctly should be able to click on that and we'll adjust that post would you look at that all done you can go back to the home page click any one post it will load that post separately with its own URL you can even see in the top or the title bar it's the correct thing there what's crazy is since this is happening on the server that title is actually correct in the HTML coming down to the client so if I was to you know actually I'll just push it and show you guys in production status commit separate SSG helper metadata fixed for or not even my data fix it's just uh full post page let me push wait the 40 to 50 or so seconds before that is deployed and uh this should be done now cool it is so the cool thing I specifically wanted to show is if we go to one of these posts the first time it'll take a second to load because it's generating it but from here on out it's going to be instant but even cooler if we look at the HTML the server sending back so not in the Dom if we right click inspect obviously that's going to be correct the react code is running on the client it's going to keep things up to date we actually go in here and look at the HTML the server sends initially response format in here the title is correct because this is being generated on the server when react does its first pass through the app in next.js the types are there the data is there all from this generate SSG helper hydrating the data ahead of time and then dehydrating it dehydrating it into the Dom that means that the HTML the client gets is correct immediately and once that's cached on the CDN it will be downloaded immediately too so this works great for not having to worry about your SEO if you want to have SEO data without breaking out of trpc's patterns there you go all the things that I need to wrap this page with all the things I need in the head are fetched ahead of time and I can block on that trivially it's so powerful I I love these patterns you can set custom refetch and revalidate times here as well no real need for that since none of this data can change but in the theoretical future where it could this is where you would put that ignore the outfit change pretend to wear where we were I have one small thing I want to add that's going to cut the build times on versel in half I already have a video about this but I think it's important to include you shouldn't be checking your types in lint on versel builds there's no reason to because GitHub CI is free and it's right there so what do I mean how do we use it I'll show you real quick I've learned anything over the years is that writing yaml files sucks rather than writing the ammo file I actually asked chatgpt to write it for us so I went to chat GPT and I said write me minimal GitHub CI yaml file that installs node modules runs typescript type checking and also runs lint the reason I'm choosing to do this on GitHub is GitHub is where our code lives and the validation of our code should live with our code the thing that builds and deploys it should be separate our actual building and deploying at least for pull requests should not be blocked on if type checks pass github's role is to make sure the code is correct versel's role is to deploy the code that we give it so I try to separate these things it makes the performance of your CI pipeline significantly better if you do so I'm going to yoink this we're going to actually we can even copy this this is the path that it thinks you should put it in and it is correct so kind of make a new file not in there here's a new file copy code paste here is environment variables I actually asked chatgpt how to do that I have this example here so honestly I can just copy the whole file again we can go in here the environment variables we need are all listed here node environment will come with the environment itself so we have to do anything there but the database URL this is the one we need to change so or we'll have something for we're just checking that it's a URL so any valid URL will work here copy the name perfectly database URL GPS fake.com cool and now we have everything we need add CI and now we have additional CI that will run on GitHub separately from versa so if we go to GitHub and check this out you'll see here we have a separate build running that isn't on versal if we were to go check on versel this build is going to take the same 47 or whatever seconds because we haven't changed anything here we just added this additional check and this check is going to run lint and actually I don't think we have a command for type check yet so that might fail yeah you can see here this failed missing script type check okay we can add that quick if you're not familiar in package Json you have scripts these are all different things that you can run from the terminal through npm and we need to add one for this so type check the easiest way to type check is to just run TSC I guess no emit is fine thank you copilot for making your code even better now if I push this add type check command now it's going to go run and build on GitHub CI but how do we actually disable it on versel in order to get faster builds it's pretty simple we tell next.js in the next config though we don't want it to do that so for typescript we tell it ignore build error is true for eslint we tell it ignore during build and we can also swc Minify true it's an experimental flag for using swc as the minifier instead of using Babble I believe which should speed things up quite a bit as well all of this should result in our builds being notably faster so we can add that to p ignore build errors on for cell swc Minify if I did this correctly we should now see separately running nice before I made this change it was 43 seconds and it went down to 28. so yeah you should expect better performance but more importantly you'll have different steps failing when you file a pull request generally I don't recommend merging straight to main when you're still working on a project solo totally fine but in GitHub you can actually lock the main branch you can go in here branches Main and set up a protection rule so you can't just push straight to main you have to file a pull request even on solo projects I'll often do this because I like pull requests as a way to force myself to review my work and make sure things are okay before they're deployed and generally it's nice to have that step and just see like in here you have different checks for different things so I have my CI for build and I have my versel environment separately built here generally really nice to do things this way the final thing to finish this project off is we need to give it a real domain versall makes this super easy you can buy domains straight from there it might not be the cheapest but it's so convenient to just have all your domains in one place and I buy pretty much all my domains on versaille now once you have it in here you don't actually have to spend much time in this page there is some magic so if you buy a domain over cell and you want to add like email servers and click the add email button and there's a g Suite hotkey there's protonmail all of these DNS records that normally take quite a bit of time to set up are one click setups which is so convenient if you've ever configured email servers before just a funnel side what I want to show off is once you have a domain how to add it to a given project so in settings domains since I already have the t3.g domain assigned with our project if I was to assign it here it's going to tell me when I try that I have to move it from Astro to here and we don't want t3.g on this project anyways that's my home page we do want a subdomain from it though so all I have to do is make up the subdomain let's come up with something funny uh we'll do bird emoji and then these are good bird emojis we'll use this one oh is it you have to like make it the special code uh I do this though haha and for any normies I'll add sharp t3.gg and I'll redirect it to the other domain cool in this domain it might not look super cool in Chrome but if you use a browser like Safari that recognizes emojis ta-da I think that's cute so yeah chirps now an emoji domain tarp.d3.gg you can still use obviously close Safari you can still go to chirp if I could spell sharp.t3.dg and it will redirect there it's all set up through versl itself directly significantly easier than managing all this yourself yeah I love ourselves domain stuff I think that's it actually for most of the core functionality there's a lot of small things I would like to do you may have noticed that I've hard-coded in a lot of places uh take 100 so we're only going to get the first 100 posts if I wanted to get more I would need to be doing pagination with infinite queries there's a bunch of documentation on how to do that on the trpc website highly recommend checking that out those query patterns are really cool for pagination and infinite scroll and a lot of the behaviors that real big apps need on top of that probably would go in depth on the way that links work inside of the post view because right now it's pretty Jank you have to click the time or the username I would obviously wrap image in a link but I would also want the whole card to be clickable to go to the Post but you can have a link inside a link so you'd have to do some fun Dom hierarchy things there totally doable but not clean didn't want to waste too much time here I would also in the post creation view which is in the index file here create post wizard this is awful don't use state for this please use something like react hook form you could even share the validator on the back end and the front end you can create one emoji validator that is shared import on both back and front and then use the same validator as the input for the mutation as well as for validating the form super fun pattern I use it all the time but I didn't feel like setting that up here because this was meant to demo how all the pieces come together the one last thing is in the future it would be pretty nice to have the database synchronized with clerk so I don't have to do all of the clerk calls it's not too bad because their service responds really fast and most of the things I'm doing here are well cached but ideally we would be actually modeling a post to be directly mapped to a user with a relation rather than the author ID string here that isn't relation to anything there's no binding of this author ID anywhere we have to on the server take these author IDs to go fetch user IDs in a true relational database we have user in here and you could do that using Clerk's web hooks to synchronize stuff but I'm going to hold out for changes I know they're working on to make synchronizing your database with clerk even easier in the future it'd be really nice to generate OG image tags that have like the post in the user's name using the versal OG image Library this is a super fun thing of meaning to play with it it's a way to generate images trivially with Tailwind like syntax to actually have metadata when you like post a link on Twitter or Discord or something that shows a nice generated image per route super cool to have proper embed support with such a trivial addition I think it even supports Talent if I recall which is super duper dope I think that is everything this took absolutely forever to make multiple days of recording and way more time spent actually building the repo and getting everything just right I wanted to make this as informative and useful as possible if any issues working on this project let us know in the comments and come check out our Discord if you haven't if you become a hardcore T3 stack user you should get something to support us this year it's the official T3 jacket we just made these I love them I went out of my way to make merch that I actually thought was cool not just another damn t-shirt so if you want to rep T3 in the workplace check these out on shop.t3.gg this has been quite the project I really hope you all appreciate it thanks again nerds peace
Info
Channel: Theo - t3․gg
Views: 334,385
Rating: undefined out of 5
Keywords: web development, full stack, typescript, javascript, react, programming, programmer, theo, t3 stack, t3, t3.gg, t3dotgg
Id: YkOSUVzOAA4
Channel Id: undefined
Length: 179min 2sec (10742 seconds)
Published: Thu Mar 23 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.