From 0 to Production - The Modern React Tutorial (RSCs, Next.js, Shadui, Drizzle, TS and more)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
can you believe it's been over a year since my last production ready project tutorial things were very different when I filmed the CHP tutorial server components were brand new bun hadn't hit 1.0 yet primagen still had a job hell I barely even had a mustache before we start I want to go over the tech that we chose the companies that are sponsoring this video and how I recommend actually using this tutorial if you want to skip straight to code you can use the chapter markers below to do that this tutorial is not designed for brand new devs I would not recommend watching this if you're learning how to code still if youve built a few projects before and you already have a GitHub you are probably good to go but this is meant to be for everyone and there's lots of good tutorials for beginners already I know there's a lot of devs who use these tutorials to learn new technologies and I think this is great for that regardless of your experience level the goal is to Showcase How I build production apps everything I do in this tutorial is based on real choices I've made when shipping real applications but yet at twitch or on my own so what technologies are we using what the hell even is the T3 stack honestly T3 Stack's more of a mindset than a concrete stack when I started using next I loved it despite realizing that it was kind of incomplete that was by Design though next is best when you use it with other Technologies so I hunted for the best ones and I coined it after my username the first ever video on my channel was actually me playing with the stack well there's a skate video but ignore that Nextel a young Community member took the opportunity to make a CLI that scaffolded a new project with all the things that I was recommending and it took off alarmingly quick since then the technology we recommend has changed a lot next move to server components prism has been overtaken by drizzle trpc has become much more optional and I stopped rolling my own off this is by Design though the T3 stack was always meant to be modular chasing the best set of tools to empower modern full stack does and letting you swap them out whenever you want that's why I'm making this tutorial though it's time to embrace the new tools that I've swapped to and recommend this doesn't mean the old recommendations are bad though I still use page router in next and I still use Prisma as well for a handful of projects I just prefer these new Solutions now and in my opinion they're stable enough to ship confidently here's the core Tech that we're going to be using nextjs alongside react and of course we'll be using server components and server actions really excited for that typescript because obviously we're the type safe cult Tailwind because of course Tailwind it makes CSS way easier shad and UI because it's the best way to handle Styles in a new application drizzle omm because I'm kind of tired of Prisma and drizzle got into an incredible spot as well as p& PM because as much as I love bun it was not fully compatible with some of the drizzle stuff yet mostly probably on the drizzle side regardless I don't want to dig into it I just want this to work for you so we'll be using pnpm for this tutorial you can use whatever you want though to be clear you just might run into some weird issues here and there the tech list is cool but we need to host this all somewhere and I'm excited to share that all of the hosting Solutions I use were down to sponsor this video well except for GitHub regardless here are all the sponsors that made this possible first and foremost we have versel who makes deploying web apps at scale as simple as pressing two buttons clerk making off so easy and maintainable that I use it in every project I build now host hog which has been my Analytics tool of choice for quite a bit and I'm really lucky that I convinced them to let me be their first sponsored Creator and up stash which is the fastest KV store on the web and a brand new sponsor today is Sentry really excited to have them on too because they're the industry standard for error management and production and now I can show you guys what I mean when I say that note that planet scale is not in this list while I still love them and I ship their databases in production every day I think it's important that these tutorials are free for anyone to follow along with we'll be using the versell postgress database instead I want to emphasize that I do actually use all of these things in production nobody paid their way to be first in line I could make much more money if I was to just pick whatever solutions would pay me but instead I reached out to the ones that I'm actually using I'm incredibly lucky that most of them were down to help with the cost in making this content anyways enough blabbing let's go write some as you could probably have guessed we'll be starting with create T3 app I'll be using pnpm you can use npm or yarn I'm not 100% confident with bun for this project just because drizzle Studio doesn't quite work how you need it to yet it might even be fixed by the time this video is up but just I'm using pnpm I recommend it if you haven't already just follow along with that if you can but that all said let's get started pnpm create T3 app at latest just guarantees you get the latest version of the package which you probably want for this now that we're creating let's select our options we want to name this T3 Gallery you can name it whatever you want of course this just the name I'm picking for the project typescript duh you wouldn't be here otherwise tailin yep trpc here's where the first controversial choice of this tutorial comes up I don't think trpc is going to help us with the things that I'm trying to teach and communicate here it does make a lot of sense to use trpc still even in app router nextjs server component projects but it's not a necessary dependency for full stack apps the way I would have considered it when building with NEX before we'll go over what I mean by that and also where trpc is still very valuable near the end of the tutorial I also am really excited for a new version of trpc coming soon that takes more advantage of server actions as well as server component design so for now no onto your PC as much as it hurs me to say such same deal with o at this point in time we're still not offering clerk as an option in create T3 app that might change in the future but honestly the setup is so simple it's not a big deal and I'll be sure to set up off with y'all as we keep going so none for that for now another controversial Choice as much as I've loved and recommended Prisma I do think drizzle is at a point where it's hard to justify Prisma anymore the quality of the experience drizzle offers the performance bump the better integration with typescript directly and the the more minimal approach to their design has been it's been a lifesaver and I'm really really impressed with Drizzle so I think they're an obvious choice here and now the whole point the thing that made me have to do this tutorial in the first place app router last controversial decision here as much as I do still love Planet scale I recognize they are not the right choice for something like this and I've come to a mutual agreement with Planet scale to let me use other databases when doing tutorials like this in order to make sure everyone can follow along without having to pay a scent which is again very important to us you should absolutely be able to do this whole tutorial for free so with that all said we can't pick Planet scale we're going with postgress specifically for cell post grass which isn't particularly hard to set up but I'll show you how to do it in just a bit should we initialize the gate repo and Stage the changes yep should we run pnpn andall sure default Alias is fine here we go T3 Gallery scaffolded successfully and now it's initialized it's time to open it up in your ID I'll be using VSS code but you can use whatever you like here we have our code base we have all of the things that got initialized as we started the project I usually like to commit this in its exact State when we start so we can do that now get commit and it cool and now we have our starting point we can also run this quick now that we have this running we can actually check it out in the browser I'm going to be using Arc it doesn't matter at all you can just use Chrome in fact Arc is effectively Chrome for this use case but yeah I've been liking Arc so I'll be using it and here we have the create T3 app home page so now that we know this is running there are two things I want to do first and foremost I think these are really important starting points that make projects like this much more likely to succeed I also don't see them included in tutorials enough first thing I want to do is make it to-do list so we actually have a rough idea of what we want to build it's so easy to get Rabbit Hole on specific things that aren't that important and if don't have a list of the specific stuff we want to complete we're never going to complete the project this also will help me making the tutorial but this is a thing I do in every project I spin up whenever I'm working on something like this so let's hop back to our IDE because I'm not putting this to-do list in some crazy random app that I'll never remember I'm put it straight in the readme so we're going to delete all those contents we're going to put in T3 Gallery I'll make a separate to-do section all caps is fine for this and now we're going to add all the to-dos first we want to get it deploying make it deploy then we want to scaffold basic UI with mock data then we want to actually set up a database then we want to attach database to UI then probably be a good point to do off at that point oh look at that my uh super Maven auto complete this is so smart these are all things I wanted to do the rest not so much so but image upload is an important one and notably doing that after authentication then I would say after we get all of that working routing and image page is important so when you actually click an image it has its own separate route we'll be using the parallel route stuff that's in the new app router and it's hard to get your head around but it's really powerful once you do so I'm actually very much excited for this part I'm not going to think of everything here I know I want to add a delete button with server actions that'll be a really fun addition uh analytics are really important rate limiting I'm actually really excited to do and rate limiting will of course be using our friends over at up stash because they built the simplest and best rate limiter in the world she also point out analytics will we post talk and honestly I was going to recommend setting up error management slightly earlier so that we can get errors when things go wrong with like image uploading so I'll put error management right after image upload and autocomplete smart enough to know that Sentry is the industry standard for a reason I'm very excited to be working with them for this video thank you again to all of our sponsors for making this all possible I'll add those widths for the other sponsors too obviously we're going to be using clerk for authentication the actual database as much as I love superbase and I'm not surprised that it got autocom completed will be using for sell post gr just so it's one less thing to set up and one less decision to make and obviously for sale will be used for deployments cool this is a pretty rough idea of what we want to do I'll likely come back here and add additional things as we realize stuff that we're missing but uh we'll handle that all as we get there so I also want to make sure we're actually seeing our app when it deploys so I'm going to just go to the default page kill most of the content here and say hello Gallery in progress cool quickly check hello Gallery in progress cool now we know this is definitely our app if we get this working get status to make sure we see all the changes here something I'll be doing a lot in this tutorial is get ad- P instead of get ad- a it seems like a small thing but the magic of- p is you're effectively doing mini code reviews for all of your changes so I can see here that I deleted the old readme and I made the change of this new readme so I'll do y to say that I've completed that and then here I deleted all that content and then I added this hello text so y to accept that as well now I have all of those changes staged I can commit those changes as a starting point and read me now I want to push but it's not going to go anywhere I could go initialize this on GitHub but I'm lazy as much as I love to crap on the GitHub see cuz it's ux isn't great as you'll see here it's a game of 21 Questions still slightly better than dealing with the GitHub UI GitHub repo create is the command we want to run and here it selects the worst possible default option of create a new repo on GitHub from scratch we want to push the existing one so make sure you arrow down and hit that path is the same T3 galleries a fine name I want this in t3g no description private for now because I don't want to share this code base till we're done add a remote obviously that's the point origin duh it's a new project origin yes push cool way too many questions there was either a bad default or an obvious default or both but last time I complained about this people were mad at me so ignore that now we have this pushed if I go to my GitHub you will see in the repositories tab that I have through Gallery here which is Javascript because we haven't written enough typescript in it yet for that to be flagged over but uh this point in time funny enough it's half and half there's a reason for the JS files I'll talk about that in a bit I made a mistake and used a similar project name and had a conflict so ignored this check you won't see that yet but you do want to see this check even if you don't have it yet because what this check represents is this project being linked to for sale it's it's actually very easy to do such you go to ver.com you sign in with your GitHub if you haven't already signed in you click the little add new button that my face is covering I'll do that so you can see it project import get repos T3 Gallery all looks good one important piece here is there's environment variables that will be missing this shouldn't block build but with create T3 app we've set things up so it will and you'll see when we deploy this is actually going to fail and I expected to do such and I'll explain why in a second and after just a few seconds we almost immediately get this failure invalid environment variable database URL required this is because one of the packages we have set up for you in create T3 app is an awesome Edition by Julius T3 OSS EnV it's the T3 EnV package you can learn more about it at env. database URL that's why it is placed under server here so without this included in the project it's not going to actually build so let's go over to the project and now we can add a fake one for now just to make sure it builds and get it online so we'll hop over to the project so we have T3 Gallery the new project with no production deployments hop into settings hop into environment variables and in here you can paste things which is really convenient and I don't just mean like paste individual stuff I mean you could grab something like this and copy it like fullon environment file syntax and it will split on the equals and fill this correctly so this is just a dummy thing that won't work but that's fine we don't need it to work we just need it to build and now that we've done that I'll go over to project well I guess we'll go to deployments because we're going to click here and redeploy because now that the environment variables have been changed this build should be able to pass request to the Google fonts failed the first time I'm sure that Google's apis love how much they're getting hammered by versell all the time but uh handling it fine cool I have some imports that I'm not using we can turn off those checks later if we don't feel like doing them in here which honest probably shouldn't but in just a moment we'll have our app built and now it's deploying how' that take 40 50 seconds just under a minute domains being assigned T3 Gallery D Olive so we'll hop over here and we see now in production T3 Gallery olive. ver. apppp this is our app and now all we have to do to make updates is get pushed and as long as we push to main this will automatically deployed if we make poll requests with changes we'll get preview builds that show those poll requests and the changes that we've made before we merge it just all of these things make the workflow so much better setting these up right used to take days to do and would be fragile and break all the time it's just one of those insane wins that makes working with tools like forell groundbreaking in terms of how much more effective you can be as a Dev and this isn't just like oh it's easier for hobby projects this is like oh building production software now has a much better overall workflow so this isn't just for noobs this is how I ship production grade software and the rare times I can't use versell I find myself recreating all of this functionality because it is so useful they didn't pay me to say any of this this is just how I feel I'm lucky I convin them to pay me to do this in the first place so now that we have that all set up we can actually start building deploy has happened let's scaffold some basic UI one of the things that we want to do here is have that mock data but in order to have that data we need images because this is a gallery so I could go put them on some random file host somewhere or host them locally but hosting your files out of your public directory has so many potential issues around just bandwidth usage that I want to avoid that to the best of my ability best way to do that is use a service that doesn't charge for bandwith at all I can think of a pretty cool one upload thing.com you didn't know upload thing was built by my co-founder Mark and me after we built ping which is a video calling service because we realized managing your files in a full stack app was way too hard we built this to make it comically easy when i e comically easy I mean people were speedrunning the setup for upload thing in next and they had it under 10 seconds it's hilariously easy so I'll show you how easy it is to set up something like this I am already signed in with GitHub if you're not signed in just use GitHub take seconds create a new app app needs a name T3 Gallery uswest is the default region you have a bunch of options now if you want to go somewhere else but you have to be a paid plan in order to do that just in case you're not already the free tiers super super generous though so you probably won't need to pay for a tutorial like this so now we're creating the app cool I could go add the API Keys integrate this in our project and be able to upload files but honestly I just need the URLs for this mock data so I'm going to go to the files page and you'll see there's a nice little upload button here where I can just click choose and upload some files that I already have here so I have some random pictures from some random thumbnails so I can just grab these just grab like a few of them there are four random recent thumbnails of mine they are all now uploaded which means they all now have URLs cool cool now we have all these URLs I can go put these all in our code somewhere I'll just do const moach URLs equals URL one close URL 2 close I want to emphasize you don't need to have four examples and I'll also leave this project up so you can use these ones if you want to but uh obviously copying these URLs from my screen is not going to be the easiest thing in the world so I would recommend just spinning up upload thing and quickly doing this it's not hard to do and it makes life much easier and now that we have that done we have have bunch of default files that we can use the data is not going to be in this shape we can reshape it later but honestly the core part is that they're all in a URL object so const mock images equals cool the ID is going to be more like the index so we'll do index plus one this is just us getting mock data so we can actually work on some of the UI I hate a lot of how this looks by default so I'm going to kill all of that um I don't even want to give the main Styles yet let's do a div inside that has class name equals Flex a really fun thing that people don't use enough in Flex is wrap wrap means that it won't stop with one row it will make multiple rows which in the case of having a lot of images can be really handy and look at that my auto complete being super smart I almost want to turn it off to make this tutorial a little bit more realistic but uh yeah it just slaughtered that I don't have a close for this div that's what it's mad about cool now that div is closed and I know I just autogenerated this code but it's basically exactly what I was going to write I wanted to put alt text at all for this simple of a demo but that's fine here we have div Keys image. it needs a key so react knows when to or not to update this thing and we have the class name which I wouldn't done with 1/2 or padding for here I guess I'll change this to with 48 decent starting point if you want space between the things Gap is magic so I'll do Gap four there to put some space between all these images theoretically yeah that easy we already have all our images being rendered here with space between the background color disappeared though I like background colors it'd be really cool if we could have one here I'm going to do a slightly controversial thing and put it where I usually put it which is on body I'm going to be lazy and just do BG black and text white fine starting point if you want to make it nice and pretty you can but black background white text I think looks pretty sharp and it's kind of becoming the default anyways and no I'm not going to charge for dark mode this time I promise so we have this established we have actual data here if we want more data there's a really fun hack I do a lot which is now we have three times the data because I just dumped Mo images here three times this is not looking like a real app like cool we have this Flex behavior that works right but this app doesn't feel real yet do you know what it needs to feel real a topnav a topnav fits a lot better in somewhere like the layout because we want this top nav to exist on other Pages here's where we're going to start getting into the modern next isms this isn't react isms the react isms around react server components we'll get to in a bit don't worry this is more about how the actual layout works so a layout applies consecutively if we have a layout here and I put a top nav I'll just make a crappy fake one of like div class name equals w full so now it's going to be full width and in here I'm going to say first layout now we see this first layout applies here if I make a nested route here we'll call this a layer two and here I put a page. TSX as well export default oh cool again Auto completes magic I want to use all this data here I'm just going to put in here uh page for layout 2 now if we go to that URL which is L 3000 layer 2 you'll see that the first layout persists if we add a layout TSX into this component as well layout. TSX I'm just going to yoink this guy I'm going to delete most of it but grabbing this will make it slightly easier to Define things specifically I wanted the children definition I don't need the rest so I'm going to delete this and this I'm going to change this from body div we don't need any of that anymore and we're going to call this second layout and here's the magic of the new router first layout applies on the outside second layer applies in the next layer and as you keep nesting these layers keep getting applied why would you ever want this well if you have something like a dashboard like I don't know the upload thing dashboard we always want this top nav to exist everywhere you are in the app well not everywhere in the app but everywhere in the dashboard so in the dashboard folder we have the root layout that has this top nav within an app we now have a slightly different URL so if we go here you see it's SL dasboard SL your app ID so once you're inside of an app we have the side naav that is another layout that is going to be there for all of the different views inside of your dashboard and now we have all the different pages for all the different actual locations here but they're able to share the side nav and they're able to share the top nav nested this type of nested layout design is so convenient and doing this in the old model was miserable so having it just built into next now incredibly overdue this was all meant to just be like a Showcase of it so we don't need any of that code so I'm going to delete that this is an okay starting point for the topnav but it's easier to break it out into a component the sooner you can the better so X I won't export it we only need here function topnav look at that the auto complete is so smart we don't actually want actually anything here now that I see this we could use a nav component it's the correct thing so yeah I guess we'll use it and justify between is nice cuz that will add the space between the elements which we absolutely want we'll have the left side here be uh Gallery we actually aren't mounting this because I'm stupid so I'm going to swap this div out and put our top nav there instead and cool now this say gallery on the left I absolutely want some padding on that so let's add like P4 it look a little less bad cool text Excel make it look a little beefier uh font I want semi Boldt for that I think yeah it looks nice border b a nice simple border on the bottom the border is there you just can't see it CU all these images are so high up so we can add that either by doing additional margin below this which works but if you know me very well you know I hate hate hate hate margin so I will not be doing that here instead for now we'll do a flex column for the body in the future the correct solution is almost certainly going to be a grid but this is a good enough starting point I'll put a gap four to force some space between these elements I forgot to actually put the flex Flex Flex call Gap four cool and now we have a little Gap there we need to finish working on this actual element though so let's do that so we have the gallery on one side the other side we'll just say sign in I'm actually going to make it work and you can't see it cu the screen size when I do that you see we have gallery on the left and we have sign in on the right honestly this is a really good point to start with this is the skeleton scaffold getting started point I had in mind so we can work with that I should go check box that we have done this too quick though so we'll go back in here scaffold basic UI cross that off get status get add commit scaffold UI get push if we want to go see this builds we can but we don't have to actually on the topic of builds I'm not going to think to do this any other time so I'm going to add one more step here tidy up build process this is two small changes one of which we're going to have to revert one of which we have to follow up with later but I like doing these things sooner than later cuz they make everything faster the first one is in the next config going to do something a bit controversial we're going to change one of the typees script settings we're going to ignore build errors we're going do the same thing with es lint too these fields we know work cuz we're importing the types from next next conf fig so if we spell this wrong we'll get a type error immediately because errors doesn't exist thankfully my auto complete with super Maven plug they don't pay me but I'm actually really liking super Haven recently so try it out if you're curious co-pilot will work fine there too but the point here is even if you don't have either I can delete that autocomplete and see all of the options so I could put a custom TS config don't worry about that we just want to ignore build errors why we want to ignore build errors and ignore eslint during build well we can run these things separately we can check types and we can check eslint in a GitHub action and not have to block our Builds on it if you want everything in one place and you're okay with your builds being slower don't bother with this step but I'll show you just how much faster it is in a second speaking of faster there's one other thing that is still admittedly early but is just so good I don't like working without it anymore that thing is turbo pack you can turn it on by adding D- turbo to your next Dev command once you do that you're going to have to restart your Dev environment but now with this command instead your Dev environment will spin up and update significantly faster I found this to be a huge quality of life win and I highly highly recommend it if you haven't already now that we have those let's actually commit those changes I should add that I did those changes here tidy up build get that one last bit commit that as uh build improvements and now we can go check on versell to see the difference honestly the easiest way to get to the versell UI for that is I go to the GitHub repo click details here command click so it Ops a different window and now we have this exact project and more importantly this exact deploy so you see the previous one took 46 seconds the one before that was 51 seconds so like not slow but as your project gets bigger and your types get more complex those build times can get a lot bigger like multiple minutes long sometimes even hours I like to avoid that I like my deployments to be very very quick I go out of my way for under 2 minutes and these little changes can help you guarantee that you'll be there funny enough it seems like Google's font API is causing issues and adding a solid 10 seconds or so here because it's failing to fetch that it works fine the next retry but the first attempt fails that said build times are still 40 seconds it should be 10 seconds shorter because of that but uh don't worry about it you'll see a slight improvement over time and now that's all configured let's actually start making this production ready as I mentioned before we need a database already in for sell let's click storage let's click create database and make a new postest database you are only allowed one postest database per free account on verell you could pay the I think it's like 10 bucks a month for the pro tier what you can also do is share one database across multiple projects if you're using a tool like drizzle specifically there's a really cool feature that we actually have enabled by default when you use create T3 app where we have a prefix and all of the table names which means that when you use postgress or whatever database technology with Drizzle here it will only affect things that start with this in their table name so you can have multiple different projects using multiple different prefixes sharing a database and it's totally fine this might sound terrible to you especially if you're like a developer that started post WordPress but if you were around during the WordPress days in the old PHP ERA this was the most common thing in the world and especially for side projects tutorials stuff like this there is no shame in using one database with a bunch of different prefixes for a bunch of different projects this is actually how I'm using Planet scale right now funny enough with that all said we can use one database or we can use multiple whatever you prefer I'm just going to use this one you can also use another postc database if you really want to but I do think the verel postgress has enough niceties here that it's worth using with this tutorial I'm just going to name it T3 gallery like obviously it's postgress if that's the product one more important piece here is the region you select I highly highly recommend selecting a region that is the same or very close to the region that your versel project is deployed in so if I go back to versel you'll see the default region that it deploys in I believe is DC we might have set a different default location but yeah no it didn't it's DC I live in SF I don't love SFO one the region just cuz it's not as stable as Portland because Portland is West 2 so I'm going to go for West 2 now all future deployments are going to be on West 2 I don't actually know if you can change regions on a free account or not but if you can pick one close to you if not East one is fine you're not going to have massive slowdown or anything more importantly though make sure the region matches the exact same region that you have for your database or you're going to have a ton of unnecessary slowdown highly highly important that you make sure the region for your database and your project are the same in order to prevent a bunch of performance and like latency issues that you shouldn't have to deal with you can change the prefix here if you want to have database URL instead of postgress URL okay lesson learned and I learned this a little bit later to save you some pain ignore the thing I just said about changing that to database URL leave it as postgress URL it makes life much easier because versel wants a lot of environment variables my bad was loaring as I go anyways and now we have everything linked up and ready to go sadly this is only ready to go in production so a couple ways we can get this environment variable stuff down locally we could use the versel CLI Honestly though I tend to do this the lazy way env. looc copy snippet hop over to my code base go here paste it we don't want the old database URL anymore so we're not using that so I'm going to comment that out but here we go this is all set up how you would expect make sure you don't share these values with anybody you don't want to see them so like right now in this tutorial everyone has all my environment variables if I don't go through and reset this afterwards huge huge liability you want to be sure these things never ever get seen by anybody who isn't a Dev working on your project with you that you trust this is important things to keep private especially for your production environment you also can set up different data bases for production in Dev if you so choose not necessarily something like this but for a real app absolutely you want to split these things up cool now we have that done we want to actually link stuff together specifically we want to link this all with Drizzle their example here isn't great but it's fine we put the drizzle client in a different place but I can still copy paste the contents here hop back to our app go over to guess it's not really our no it is our index you'll see in our index here we're doing a bunch of stuff to get environment variables and make sure they're all being applied correctly so we can connect to the postc Cross binding we don't need any of that delete all this realize that we haven't installed ver cell postgress yet so copy hop over to our terminal pnpm add paste back here we don't need to Res the server cool picked up on that already we see SQL con DB is drizzle SQL one more thing I want to do here is include the schema which we had defined before actually I think the schema should be coming in from here shouldn't it AR you exporting that we are not so I'm going to command z a little bit and see what we had here before and we see here import Star as schema from schema okay that makes sense import Star as schema so the reason that we're doing this import there like that is it makes life much easier for querying against your drizzle database the schema and the glob import here is grabbing all of the different models that we have created here so right now we just have this one table of posts but if you have a bunch more and you're exporting all of them now we have all the parts we need to Define our schema and then use that and consume that here what do this matter about unsafe argument of type any assigned a parameter of type vers LPG client sometimes you need to reload the window of types scripts being stupid enough yep that was the issue everything seems to be fine now yeah no idea those was probably just because of the package being installed now we have con DB equals drizzle using that and this is using all the fancy for cell post stuff as well we actually need to make sure the database is in the same state that our code is though so here we have posts which is like the default that we ship the project with we'll be changing this in a bit but just leaving it like this for now helps us make sure stuff's working as expected we want to make sure this is in our actual database though the way we do that is using a command that we've actually provided for you already DB push if you worked a databases in other projects before you're probably pretty familiar with the idea of migrations you have a folder in your project with a bunch of SQL files that run a bunch of different changes to get the project up to a certain point I don't love migrations I find that they fail to acknowledge a lot of things that are necessary both for scaling your databases but also for using these things with a team where like I have a bunch of diffs on git that represent how these things change in the order they changed in migrations don't encapsulate that properly they come from a prever control era and I don't like using them as such we recommend using DV push there's a lot of content about this that you can go check out just we're using DV push for this remember when I said earlier that I screwed up by putting database in the prefix here yeah I did we have to go make a couple subtle changes to fix that the easiest way to make most of these changes is to hop over into the environment file select the word database command D till all of those are selected not the database uncore database cuz dumb we want to change that to postgress the way it was set up by default and now that we've done that all the environment variables that vel's built-in environment variable Checker expects are there there is a catch though which is that other things in the project expect database URL easiest fix for that command shift F and replace all database URL calls with postgress URL this will change the enjs this will change the start database which we never going to use that script anyways but it will also most importantly change the drizzle config so all our migrations will still work as expected apologies for the Jank Cuts I made a mistake and I figured it'd be easier to put this here than have you guys getting errors that I'm not getting in my Dev environment so once you've made this change where you swap all database URL calls with postc URLs you should be able to DV push and DB Studio fine so let's actually do that push which as I mentioned before will update the database to match our current schema pnpm run DB push done cool if we want to actually see the stuff in the database we could go write a page or an endpoint that uses this data but I'd much rather just use another command we added which is pnpm run DB Studio DB Studio takes advantage of the drizzle Studio project which is super cool they'll also give you his custom URL locally local. drizzle. studio and we see here T3 gallery post this is the table that we just created and if I want to add test data in here it's trial to do such I'll do hey Please Subscribe as the name leave all the things default save cool now we have created out name and ID if I actually want to see this in our project though we have to write some code so I will go back over to our main page TSX and make this async because we're going to have to do an A8 const posts equals await DB we have to import this not from versel postgress but from server DB in our project because we want to use our database stuff db. query I think you can just go straight to db. query. poost yep doind many cool and now we have posts let's just console log this to see it hop over to make sure that we're actually running this in Dev cuz I killed my Dev server earlier I will once again run pnpm Dev make sure everything is working as expected and hopefully theoretically I can refresh this page and I don't get an error remember that console log that we added wait where's my console log this is all mad about not having or about having keys being reused um I'm going to get rid of those errors really quick it's issue of me doing this stupid Loop so we're going to do a thing you're never actually supposed to do which is use an index as a key that is fine that'll make those errors go away oh because this isn't the string oh JavaScript easy fix here now it's cast to a string and now none of the ideas will match cool so all the errors are gone sorry about that now the errors are gone we notice this console log is in here and finally after all of that rigoll of me making mistakes I can show you the thing I wanted to show which is that this component is actually running on the server not the client this is a very very important distinction because previously in react land your components ran on the server as well as on the client when you're doing serers side rendering now with server components this only runs on the server which is a huge huge difference because it means I can do a database call and not have to worry about it I can even call SQL directly here so a lot of reasons you wouldn't want to but you can let's actually use this data now right now we have these mock images I'm just going to make another map above it post. map post cool that was the right amount of friends is Max we don't have a key post. ID cool and now we have hey please subscribe if we do more posts those will all appear here as well we can confirm that by going back to drizzle Studio adding a record Another post saving and back to our app and refresh and now we see another post dope but we don't want post we want images first this is in a good enough state that I want to commit it though so let's uh spin up a terminal that's capable of that I'm going to kill drizzle studio for now get status we got a lot of things to add the en sample changed connection string changed we added for self hostress has a handful of dependencies totally fine server DB got added that got added vir variable changes database setup and now I just remembered I made that mistake with the environment variables so this is going to fail for me but if I go yink these and dump them this is a thing you shouldn't have to do unless you made the same mistake of me of naming your environment variables with the prefix and not realizing how much that broke but uh yeah this shouldn't be a problem for you can you believe this is my third time building this project and I'm still making dumb mistakes like this software is not easy boys now all of those are added the first deploy is probably going to fail because they weren't there oh I forgot to push cuz I'm stupid cool now we're pushing that build should go just fine and as promised we are now deployed we can go to the URL and see nice and clearly this works and the data is coming from the database the URL is production this is in our local database but I can go make changes using drle Studio One More saved oh I wasn't running dle Studio locally anymore so we lost that so let me rerun that super quick pnpm run DB Studio refresh this I'm easy to retype so I'm just going to delete hey please subscribe because you've subscribed by now right you're an hour into this come on subcriptions are free now that we've done that we go back here refresh and we see one of my favorite NEX isms which is that when you deploy a next J application this page gets cached on the server so the first time somebody goes to this page it's created even though though I'm hitting refresh and we should be getting a different page content here we're not because NEX doesn't know this route's supposed to be dynamic you can tell it hey by the way next this route is always Dynamic you do that by doing anything in the route that makes it clear this is specific to the user so if I called request in here somewhere which is a next helper to actually get request data I don't know if there's one for request but there's headers where I can use next headers which means I'm now doing something theoretically different every time a user goes to this page which forces the route to be dynamic if I use author things like that same deal we don't need any of that because we just want the page to be dynamic something I should have had you do much earlier in the project that makes stuff like this a little more consistent is selecting a typescript version I know it sounds silly but by default typescript doesn't use the typescript version for your project it uses the one built into vs code we want to tell it no use the one that's for this project don't just do this in literally any project because theoretically someone could be using a malicious thing for typescript that could be used here instead we know that this is just using the latest version of typescript because we installed that via package Json you're running all this code anyways you're fine press enter on that and now we're using a different version of typescript but this one includes all of the nextjs findings and things like that they built into the nextjs plugin so now if I do export const runtime we'll get autocomplete for all the different options we don't want to do runtime though we want to do Dynamic and look at that our auto complete smart but I can also do this and we see we have Auto Force Dynamic error or Force static our goal here is to force dynamic because we want to make sure every time A Change Is Made in our database this Page's content is updated on the next visit so by doing that going back over here get status get add- a because I'm also going to add that vs code change which is just part of the folders config commit um what do we just do we added Force Dynamic plus vs code stuff cool get push that guy up and now theoretically when this new build is ready we should have an app that's actually Dynamic now we have the deployed version it looks like it only has another post there so if I click it we see old page has the old data but this one has just that if I go in here and I change this to Dynamic works now save this change we go back here refresh it Dynamic works now also that page loads hilariously fast because again the region the server's in and the region the databases in are the same and as to spin up all these connections and other things it's just it's really fast it's really cool but now we can go check off an important and very stressful box our database is set up look at all that progress in just under an hour not bad right oh I guess we technically attach the database to the UI but I meant a lot more here specifically I meant building the actual schema for the database which thankfully won't be too complex going to close all the stuff we have opened because it's nice to have a a clean IDE I'm known for going out of my way to keep things probably too clean inside of my Dev environment yeah we don't need any other the files we had opened before we just want schema. TS I'm going to be hilariously lazy here we're going to select post command D we're going to change this to image we have images which is on the image table so this will be T3 gallery image in our database but we need more than this I still want to keep name because name is useful for indicating that a file has a name I also want to make sure that it's not null because we don't want to have a file in our database that doesn't have a name we also don't want to have one doesn't have a URL since we'll be using upload thing we can theoretically limit the URL length to a very small number I'd prefer to just do a big one so I'll do 1024 for the URL it would also potentially be nice to index your URLs if you were ever going to do a database lookup via the URL to find the project that it's from but not a big deal all things you can add and remove later and unused indexes are not great now that we have this done we have the table updated we actually need to persist these changes to our database which is pretty trivial to do again be careful when you're doing stuff like this against your production database because this change is actually going to break production I'll show you what I mean in a second you can might even be able to part of this here where it's trying to find many on something it doesn't think exists so pnpm run DB push it's going to give us warnings because it's a destructive change first is going to say are we renaming another table or are we making a new one we could call it a rename because technically that's how we built it but this table is meant to be new and have different data so I'm going to do create table instead yes I want to remove one table now all the old data is gone that's fine it was one mock Row the scary part now is if I go to our production deployment application error server side error occurred because the code in production thinks there are different tables than actually are in our Dev environment when you're working on a project and haven't shared it with anybody yet this is totally fine but this is why it makes sense to have a different Dev environment and production environment for your database so when you make changes like this it doesn't break the thing you have in production also I refresh this T3 gallery post does not exist anymore dzz Studio doesn't know what to do with itself I probably have to relaunch it since we made those changes look at that T3 gallery image is here remember we have all this mock data what if we just go add that to the database and we don't actually need to use mock data for the data let's uh collapse the sidebar cuz taking too much space copy over here add record we'll just do four for that paste that name 1. PNG grab this one 2.png paste the URL let's name these all ahead of time PNG 4.png back here copy paste copy paste now we we have all this mock data in the database it's funny it did not put those in the order that I put them in here I can change the file names to make it easier to see in our actual project though cool save those changes and now the names all match their IDs isn't that nice we actually want to use this so right now we're doing query post post doesn't exist I delete that autocomplete recognizes its images now change posts to images delete this weird thing we were doing here because we don't need that anymore delete all the mock stuff cuz we're using that anymore either and now instead of mock images three times we could just use images three times we also could just use images once regardless we did it nice and easy we're actually getting data from database now we know that because well I could show you will Flex Flex call our div here and I'll put another div here for the name and now they all have their names underneath them one other thing I want to change about the query here is order by default the order is going to be from the oldest item in the database to the newest we want to that we want to go inverse on IDs how do we do that well J has a bunch of Handy helpers I'll admit not quite as elegant as the things that we expect from Prisma but still pretty solid ergonomics wise here we want to change order by and here we get two values we get the model and we get an object that has a bunch of helpers in this case we want the descending Helper and that's it descending means descend you can do ascending as well you could do like sum and all these other helpers that exist inside a drizzle I guess it's just Ascend descend and then a custom SQL call we want to descend based on the model's ID this could be whatever name you want this is just the actual shape of the model we're using so you don't have to import it from somewhere else but this little syntax makes it nice and easy to Define which order you want things in if we go back to the page we see 4321 4321 4321 now see it's not better this is all linked up now we're also fetching files for everyone so this is all of the images on the service obviously we want the service to be specific to the user that signed in but in order to do that we have to have the ability for users to sign in so let's go take a look at our to-do list and you'll see now is a great time to add authentication I'm going to quickly save the changes that we just made and push them so we can make sure they're working in production get add- p checkbox is done new formatting there table changes comma that wasn't there before uh now we have actually linking the database to the UI get push and let's go take a look on for cell make sure these things are actually getting built Gallery deployments almost done another successful deployment only 35 seconds those build times are still nuts cool H over in the production build and we see we have all our images coming in what we don't have is a working sign-in button still just text it's not doing anything fact we don't have really any client behavior is right now time to fix that close close we can close the GitHub for now upload thing we can close that deployment we can close we need to open something though I said this at the beginning of the video I'm going to say it again now check the pinned comment to see if this has been changed I am using the beta clerk version because there's a lot of things I did not like about the last clerk version that they have fixed after me pestering them for a long time I will also insist that if you're not interested in Clerk and you don't want to use off that costs money there are plenty of phenomenal free and open source Solutions now you can check out next off you can check out Luchia both are great I use clerk because it is better turns out when you have a team of hundreds of people being paid a lot of money to full-time focus on making a really good off platform they succeed and I have found life much easier since we adopted clerk you want to see what I mean you can check out upload thing it flies and Clerk's a huge part of why I just went to the beta clerk docs though why' I do that well clerk is about to overhaul their entire core is going to be part of their version five but the important part is clerk core to the actual core to how clerk Works including the middleware the off layer and all of those parts has been overhauled the result is faster but more importantly it fixes a bunch of stupid that they did before the big thing that they did is they would block routes by default via o so if you weren't authenticated you couldn't go to certain pages on your project I think that's bad practice there are use cases where it makes sense for like internal dashboards and stuff but for the most part you should be checking on the page whether or not a user should be able to data on it rather than checking in middleware if a user should be able to go to the page in the first place it's just not a good pattern for authentication and I don't recommend it they finally realized they were wrong and walked it back this is a much needed upgrade and I'm excited to Showcase why they have an upgrade guide which is a nice way to do it they even have a CLI that will help you through all of the specific steps if you set up clerk the old way but we're just going to skip that and set it up the right way here they have nice simple example which we're going to yoink this goes in the Middle where TS but first we have to actually install CL I'm going to open up the migration guide so they have a upgrade guide we're not upgrading we're starting from scratch but that's fine because all of those parts will be good we don't need to install the newest version of react we're already on it with next we do need to add the beta nextjs binding though so we're going to click copy on the pnpm command paste run they have the upgrade helper which we don't need the big thing we do need is the new middleware so if we hop over to the example here I honestly like this one from their docs quite a bit I'll sure that all of these things are linked in the description if you just want to copy paste but honestly I can go through this line by line and I think it's actually pretty helpful we want to go into source and this is important because there is no good error message if you name this wrong I learned this on my first attempt filming this video where I missed the e in middleware and it took me 20 minutes to figure out what was going wrong so make sure middleware dots is spelled correctly cuz otherwise you'll go insane here we have a relatively simple off function here we have a custom is protected route which is a route matcher that uses the syntax for seeing if you're on the dashboard route and then we check if is protected route then we off protect otherwise we don't do anything nice and simple thankfully we don't really care because we're not going to be protecting any routes at least not yet and certainly not in this traditional sense but regardless this default config is fine we have the proted routes we export default clerk middleware that runs and we have this config which is the default to this is the exact same as it was before so if you go and grab the exact config code from any of the existing like clerk examples or their docs New or Old this is the same this is just saying to next hey we want this to run on pretty much everything which allows for your off to be much more consistent I could go really in depth on how clerk is doing this off just know that it's not actually going to slow down every single request it will slow down the first one every minute or so and the rest will be basically immediate this new clerk beta is also way lighter in terms of like the package size and the runtime costs so it's a really solid option I'm actually much much more hyped about the clerk beta than I would have expected now that we have this done we need to actually go implement it in the app so if we just look up like the standard clerk docs you'll see how easy it is to set up the next stuff quick start next and here is the part that hasn't changed clerk nextjs this goes in your root layout because your root layout wraps everything and you probably want your off in everything so we'll just put this import up top here clerk provider copy scroll down wrap this cut paste save cool that's it off is done there a lot more we can do with it at the very least we have authentication in the like in quote sense working in our application we want to do more than we want to actually be able to sign in so we're going to add some more components to the topnav honestly now is a good time to break out topnav and its own component I'm going to introduce you guys to a fun little pattern in app router the underscore when you use an underscore in front of a folder name in app router you're telling app router by the way don't include this in the routing so if I put a page. TSX in here that will never become a page this is just not accessible outside of it being imported other places this is useful if you just want to tell next hey nothing in here is a route don't treat it as such so you can have these little component folders that are used for things related to the specific route you're on not a big deal you can also use like a global component folder too but I like keeping these more specific to the current area components separated in this way honestly I would keep it all in one file if it wasn't for the fact that sometimes you need client components and server components and when you do that you need separate files whether or not that was the right design decision we could argue about all day I honestly think it was while we're in here and cleaning things up I can't help but notice we left this is create T3 app let's quickly change this to T3 gallery and we'll change the generated by as well generated by a loyal subscriber to Theo on YouTube cool that's all done hop back over to our top nav that we've exported we want to make this a real sign in though I'm going to leave this all within one div so that we're sure it gets shifted properly to the right with the justifi between call for the flex box we'll do two really handy components signed out this is a component that NEX provides that is only going to work if you're signed out so whatever children are within this component only get rendered if you're not signed in so if you're signed out we'll render the sign in button and if we are signed in we'll instead render the user button make sure those are all imported from clerk oh we forgot one more important piece we actually haven't set up clerk that would be useful wouldn't it go to the clerk site dashboard we need to set up a new project this little button here create application I already have a T3 Gallery app so I should probably rename that whatever we'll just do gallery for this one I'm going to turn off email signin because I don't want it I want this to be nice simple with oo I like GitHub sign in so I'll add that too but how cool is it that you can just scroll through here and add any of these options without having to do any additional configuration and once you want to set things up with your own keys in the future you absolutely can you in the environment variables hop over here paste since these environment variables aren't being used by code we write so much as by the package we've imported we don't have to add them to the enjs if we want to be sure they're always in the environment we get a good error we can but as we just saw when we were running this locally we got a pretty good error anyway so like it's not the biggest deal if we don't see the error missing clerk secret key or API key because of that I'm not too concerned that said before we forget I'm absolutely going to go add these on versell verell T3 Gallery settings environment variables again you should have different clerk environment variables in production and development we're not doing that because this meant to be a tutorial but uh yeah once you're really doing these things be careful also next public the prefix in front of environment variable means this will be part of the bundle that users get so never put sensitive data in a key value pair in your environment variables that starts with next public because this is going to go to users in this case they just use that for identifying which app they should be authenticating against the secret key is the one that actually uses your backend stuff this one is safe just don't put the next public in front save that for all environments you didn't have to do that for Dev but you have to do that for prod so I just figured why not do it now and here we see we now have a real sign in button it looks exactly the same because they're honoring the text like size as specified above when we click it we go to this page with GitHub authorized Clerk and now we're in and you can see if I don't have my side there see this little guy isn't that nice Tada and it's all just built into clerk no work had to be done to add account management the ability to connect and disconnect different accounts all these annoying to setup things just comes with clerk it be nice if everything did that I know it's crazy to think but yeah o is done again I think Clerk's surprisingly great and this is all of the reasons why so let's hop over to our readme we added authentication obviously we want this in our actual project I'm just doing egg we added new files and- P does not also include new files that you added and I want to make sure that this is working as well while this is deploying I'm thinking of other things I want to do and again this is why I like having a list like this because it's really easy to to break things up where it makes sense to so one thing I was thinking of is taint server only we'll we'll go in detail what I mean here in a bit but I want to make sure that I'm introducing patterns that make using the server component architecture incredibly maintainable and right now we're just inlining stuff in our function components we can do better and I'm just calling out here that I want to do that so now we have this deployed if we go back to T3 Gallery Olive you'll see we have a signin button in production on our production URL sign in continue with GitHub authorize Tada Isn't that cool that it's really that easy to set up off we should probably do more with set off though like right now the homepage just shows all of the images regardless of anything so let's fix that quick that's just going to annoy me if I don't we'll do signed out div class name equals WF HF text to XL please sign in above make sure actually import the signed out component and now this guy with all the actual contents here I'm going to break into its own component it makes life easier we'll be moving it somewhere else in the future uh images turn cool as M do actually have the images because those are here cut paste I forgot to make it async images and now signed in have to import signed in as well but now if I go back to my Dev environment which by the way if you haven't already made this mistake always make sure you're in local Dev when you're actually testing Dev stuff so one of the cool things I like about Arc is the URL actually appears and has this bright color when you're in Local Host stuff so you're less likely to make these mistakes cool so I'm signed in right now if I sign out please sign in above I wanted the text centered so let uh text Center nice please sign in above cool sign in GitHub authorize and now we have our images back Isn't that cool let's be real it's so cool that it's that easy to set something like that up now like surreal I'm going to commit that too CU those are important enough changes get status get add- P get commit DM uh use off or for homepage cool I also remembered one more thing I want to put in the list uh I'll put that after t uh use next image component that one has complications but it's also literal magic so I want to make sure we have that included in here now we want to actually add image uploading which means I want to have individual users able to authenticate do an upload and have that appear on their homepage this is where things usually would get more complex specifically once you have to pull in S3 but uh we made a thing for this we made an upload thing for this and I'm really proud of what we build with upload thing I know like obviously I could sit here and say I'm proud of all of my stuff but we made upload thing because doing f management was way harder than it needed to be and we wanted to fix that and I really honestly think we did and when you follow along for this part I I think you'll understand why too API Keys copy hop over to our code base go to the EnV paste now we have our environment variable set up for upload thing actually getting it set up pretty easy too I'll just go to the Docs nextjs app router cuz we're using the app directory copy this command P this command we already added the environment variables and here is where things start to get fun I'm going to copy and make this file first but I'll explain some cool stuff as we're doing it so if we hop over here we're going to make a new folder well you can also in vs code really handy trick make a new file if you do slashes in it it'll make the paths for you so if we do API SL upload thing SLC core.ts paste save it just does that correctly which is really cool this o call is using a fake o function so I'm just going to immediately delete that and use the actual off function from Clerk note that in the beta the off function comes from nextjs server not just clerk nextjs this means they don't have to import the whole clerk nextjs package every time you just want off which makes everything surprisingly more performant also user ID is its own named field on the user object that's returned here and I'm just going to make sure that user ID doesn't exist and throw if they're not authorized cool so now with this image uploader which we'll explain in detail in a moment they're actually authenticated with those few file changes it's actually that easy previously if you wanted to do an API endpoint in the pages router you'd have to use their special / API folder in order to expose kind of janky Express like syntax it was Express but Express isn't a standard now they're using this stuff with web standards and it's surprisingly easy to Define an endpoint I'll do a quick export function get this will have a request and a response object in it but I don't even care I just want to show return new response sure hello world and now if I open up a terminal and I curl Local Host 3000 SL API upload thing hello World Isn't that cool is that easy to just Define an API endpoint inside of your next project you just make a route. TS and now that will be what gets hit when you do get I can change this to be post same deal why does this matter though well we have to actually expose upload thing so that the client can use it for stuff because it's a client server relationship that's somewhat complex and we want to make sure we can handle all the things that your server and your client need to do to authenticate and manage file uploads correctly actually linking this is pretty easy once you have your router so we'll scroll down a little bit we see the route. TS contents copy paste and we don't even need the custom configs we're using everything with defaults here we're exporting a get and a post function that we defined for you with our route Handler using your file router should probably break down what the file router is now huh so we can understand how this all comes together the file router is kind of like a definition of all the ways users can upload files in your application so if you have one place where you have like posts where a user can post something like Twitter or Facebook and you limit them to up to four videos or 10 images can be posted you would Define post uploader with the right syntax here for defining what you are and aren't allowing the max file sizes Max amounts Etc all of these different keys are different things a user can upload to each of them has their own requirements for what types of files can be uploaded their own types of middleware for how you authenticating the user and deciding if they can or can't upload and also whatever you return here gets linked to the on upload complete so when the file upload is done you still have this data and then you have the on upload complete which again is different for every single one of your endpoints but allows you to do whatever you need to do in this case we're going to want to persist the file you uploaded to your database getting all of these things set up correctly in the past was incredibly difficult and would also require that you upload the file to your own server which we avoid because we're using pre-signed post URLs to allow the user to upload straight to us at upload thing so you don't have to eat the inest and output costs either so no egress costs none of that chaos and also no worries about a file being uploaded and you never being notified because we call this on upload complete for you via our web hooks so how does this all actually come together like how do we use this image uploader not going to change any more code in here for now we'll come back don't worry we actually want to do an upload we need a component to do that though ideally a component that has an upload button we could use the tail and wrapper which is really really cool especially when you're doing custom styling of our elements I'm going to be lazy though we're just going to use the import that we can throw in the layout I will say that if you're using turbo the Tailwind wrapper isn't functioning at the moment I'm recording this I think the pr that fixes it is up though so by the time you're seeing this that might even be fixed but for now it's easier to just put this import right below our Global CSS Styles and now our buttons will be styled properly scroll a little further create the upload thing components recommended yeah highly recommended hop back here I think it makes sense to put this in a utils folder Source new folder upload thing. TS paste save and now we have an upload button in drop zone that are using the type definitions from our file router why are we using the types here well if we want to use this upload button now anywhere else in the code base it's just going to work it's magical the the cool thing here if I just show you I'll actually throw this in the top nav and I'll put this next to the signed in state so I'll do a div in here actually I don't even need to do that there I'll do this uh class name equals Flex Flex row and now inside of here I can put upload button it's going to want to import from our package don't do that import from your codebase the util upload thing why are we getting a type error here if we hover over it you'll see endpoint is missing so if we start typing in here endpoint equals we actually get autocomplete for all of the different endpoints that exist in our router so if you were to go rename this or remove it from the actual upload thing config on your server side on your back end I change this from image uploader to image upload and save you'll immediately see we're getting a type error in the top nav because image uploader doesn't exist it's expecting image upload it does not it's not a sign old Type image upload cool so if we go back core save now we're good and now we see our choose file button it's a little ugly but it'll get the job done you can click here you pick a picture Let's do an older one so we know we actually got a different one Epic Microsoft meta cool the image has been uploaded we won't have any real proof of that because we didn't do anything with it but if we go to the terminal here we'll see successfully simulated call back cool cool cool we have this uploaded file and we can command click it and here's the file we just uploaded but as I mentioned before we didn't do anything with the file so if I refresh this you're not going to see anything here where would we do that though because this at least theoretically should be a client component we're handling that for you with the upload button but this isn't server code we can't really write an oncomplete here because that would have to run on client so how do we actually do something well again this is the magic of what we built with upload thing on upload complete runs on your server when the upload is done so here we have all of the data as well as the metadata for the user user that did the upload which we'll use in a minute but for now I just want to showcase all of these things coming together properly so let's throw this in our database await DB import from server DB do insert since we're doing an insert instead of a query we do actually have to use the table definition which we can import images and I want to insert values here we have a name which is file. name as well as a URL which is file. URL now we're done now this will get inserted I'm just going to delete some of these comments in the way cool we go try another upload quick cuz it really is the simple let's do uh the new react native architecture thumbnail cool wait where is it I don't see it anywhere I refresh it appears though so how do we make that appear when this is done oh boy we got a lot to do for that one not actually that much just like a couple changes in mental map model things I want to help you understand before we do all of that though I want to go kill that repeat code because we have enough images we don't need this to repeat anymore so we'll just change this to be images and now you don't have to worry about repeats either so we can get rid of all this junk with the IDS and just delete that and use image. ID you don't need the index anymore and now we see this all appeared so we want to do something once the upload is completed we have an on upload complete here on client upload complete and here we would want the page content to refresh so we want to do something inside of here before we can do that though we need to make this run on client thankfully we're not doing any server side stuff in this file so doing such is actually pretty trivial you just go to the top use client cool now this file runs on client see if I wanted to break this out earlier if we hadn't done that then we would get errors when we run this so now we want to actually have the ability to refresh the page contents we're going to use some really cool patterns with the next navigation router stuff so const router equals use router there are two again things you can import from so be careful you want to make sure you're importing from next navigation here not next router which the name something differently it is what it is we're actually going to do with this we're going to router. refresh router refresh is kind of magical it basically reruns the current route you're on on the server and sends you down the necessary parts to update the Page's content so now when the upload is completed we'll refresh this on the client side invalid State readable streams already cool I don't care what you think is wrong you're wrong close that refresh make sure we're not getting any errors anywhere that all appears to be working as expected let's do a test upload we'll do uh the HTM X logo why not sure it'll fit the page great and now it appeared isn't that crazy once you have the used client on the top it's literally two lines of code and just to Showcase what I mean with the use client being forgotten if I rerun this you'll see error use router only works in client components hooks need to be in client components because hooks are all about the component updating once it's on the client's device yeah it's nice that the errors are now good enough that you get that feedback as you're doing your work so now we have the ability to upload images but these images are uploaded for anyone to see if I was to switch accounts then everyone would still be able to see all of the images which is not what we want we want a personal gallery for just you is the individual that did the upload so we need to mark that in our database somehow so let's hop over to our database we're going to add one more additional field which is user ID this is recommending doing traditional database reference models which yeah makes a lot of sense for complex data models but since clerk is our off layer and we have such a simple binding between our off table and our actual like image representations here we can just do a a care for this it's fine and they'll never be that long but again it's fine so now that we have this we actually want to use this user ID and assign it when we create a new image so if I go back to the upload thing core you'll see something's missing here because we're missing one of those not nullable Fields there we go since we returned user ID here in the middleware function we now have access to it in the on upload complete function this is important because on upload complete is not being called directly by the user so if you used like the off helper function here you're not going to get the data you're looking for in on upload complete so you need to run this in the middleware which the user called now you have the data which exists for when you do this call later just wanted to include that because having access to this data might not be the most intuitive thing but that's why we built this return metadata pattern almost think of it like react hooks with use effect having the return that runs things slightly different we wanted to make this chainable and as simple as possible where the return from here is accessible on the input here and now it's done we have to update the database model which uh sadly is going to cause the existing data to go away but that's fine we can start reuploading all the images immediately afterwards so let's let's actually update the database pnpm run DB push it's telling us that we're going to lose data because we're about to add a non-nullable table with with no default value which contains six items so we made user ID not nullable but there's already data in the database so if we added a field that isn't nullable what's going to happen to the old data it's just going to delete it the right way to do these types of migrations if you want incrementally move to these things is don't make it non-nullable let it be nullable make the change backfill all the data and then in a later change change it to be non-nullable I don't care though we're just going to do that now we're done and if we go back to our homepage you'll see there is no data because of course there isn't data yet we haven't done anything that would allow there to be data yet so let's upload our first images well first image Let's us do one at time right now which I can show you how to fix it in a second uploading uploading uploading and now we have react native flutter image pretty cool right but we want to do multiple and I want to show you just how easy it is to do that 4 megab is probably a good Max file size but we want to increase the max file count we'll do let's do a big number 40 oh I didn't update the client side yet so refresh that and it updated you'll also see that the button has to flicker in I'll fix that in just a moment but here we see choose files images up to 4 megabytes Max 40 cool click and now I can select let's just do these two and node next I can upload multiple images at once now and it's that easy to do it tada isn't it cool how easy this stuff is now that button is hideous so I'm going to want to fix that in the moment but first I promised I would fix the way the button flicker in so let's do that it's actually back in these docks that we didn't finish cuz things were so fun to play with here is one of the crazy hacks that I built with Julius are upload thing SSR plugin the way this works is during the SSR process we embed some data in the page so we have everything we need to know what your buttons allowing to upload so we say here based on what you define in the back end images up to 4 megabytes Max 40 if we want that data to be there as soon as the page loads you just go to your layout TSX at the rot you find somewhere in here you want to drop this I'll just I think technically has to be in the HTML so I'll drop it in there import our NEX SSR plugin import the extract router config and import our file router this one comes from your codebase API upload thing you get the idea now with all that imported we go back here and refresh the button is pre-loaded and will always have the right content even if the clerk signin button sadly does not have that same awesome Behavior it is funny to me that there's more layout shift from the clerk button than from our upload button component even though ours is more complex but uh yeah call out to Clark fix your pre-loading behaviors it's not that hard to do anyways now we have this button with all the server side stuff pre-rendered all the behaviors working as expected and one more step before I forget because let's be real you've forgotten to copy the upload thing secret and app ID hop back over to versell cck T3 Gallery settings environment variables paste save and now with those environment variables handled we are ready to push this up status again I'm adding everything we added so many new folders load thing added did I get push even get persisted there cool we have now pushed up those changes and with another service added once again I think it's important to make sure it actually deploys and works closing all these tabes we don't need them anymore 58 seconds later we have now updated our production build if we hop over here we see I'm signed in we have the choose files button all is expected we'll click that good old choose files we will upload something that is different so we know it worked Apple USA cool it's loading and in just a moment it's uploaded Tada we now have a production service that delivers the core promise of this tutorial which is we have images uploading that said I think we're just getting started and the fun stuff is about to happen this is a lot of the boiler plate for getting these parts together but things are about to get a lot more advanced we're going to learn all about the taint which is the best practice for handling your server behaviors in your react applications next image which is making those image components significantly better error management routing God the routing stuff's so cool the the stuff we're going to be doing there is great so I I could rant about all these things if you're looking for a time to take a break now is probably a good one because we're about to take a big pivot as we dive all in on the taint back for my break and no my hair didn't change don't think about it too much anyways we're going to start implementing the taint but what am I talking about what the hell is react taint well uh you might have already seen my video about it if not I'll do a very brief overview this is from Sebastian from the versel team this is a little bit about how to think about Security in nextjs the key piece from this plog quest to understand is the idea of the data access layer they give three examples of how you can get data from the server on your applications using something like next the first obvious solution is just traditional HTTP apis the second example which is what we'll be moving to is the data access layer the third which is what we were just doing is component level data access so if we go to our component on the page our images component you'll see here that we're directly awaiting db. query. images is Du find many this is really nice for quickly scaffolding and setting up projects like this but it means it's really hard to like manage and maintain consistency and security within your code base people were always really concerned when they saw that silly example of having some sequel inside of your component like this they really misunderstood because they thought this code was going to client it's not the security issue here has nothing to do with the fact that this code exists in a component it's just that your location where all of these things are happening is kind of spread out across your code base instead of living in one specific place the pattern that I highly recommend that we've been using in most of our code bases is actually a little misleadingly simple we just put everything in this server folder so if we're doing queries we have functions like this we want to make sure that these are exposed and securely written we put those in a separate file in server so what I would do here is just put queries. TS and one more important piece they actually detail this in said blog post of server only server only is a package that isn't actually much of a package it's very very simple a lot of people seem to think when you put use server at the top of a file what this means is that this file is only on server the same way like use client is only on client neither of those things are true use client means that it ships JavaScript to the client but the code still runs on the server use server means it's exposing an endpoint for the client to hit we'll be using this in a bit specifically for when we're creating the delete functions but that's not what we want here cuz I want this file to only work on the server side and never work on the client side the way we make sure if that is import server only since we have this package here we should probably go install it quick because otherwise this will not work at all so let's do that super fast copy the name up here get one of my open terminals pnpm add server only cool that is done now let's actually write this query so we want to get all of the images based on not much data here just dbquery images find many for all the images so let's just yoink this for now cut export async function look at that I'm not going to trust the autocomplete perfectly here we can just do that ourselves obviously I have to import DB and now we're done well not fully done I have to go back here and actually const images equals a weight get images and obviously import this as well this might seem like a kind of silly and arbitrary change I know I'm one to say like keep things in one file where it makes sense small and silly as this seems it is actually really nice for maintaining your code base over time having one place where all of your queries live makes it very easy to audit optimize make changes and see where they persist stuff like that otherwise what you'll end up with is 15 places with this await db. query. images call instead of this one function that you hit Instead This is particularly important now that we're going to make changes where this only works for one specific user because right now if we switched user accounts we'd still see everyone's images because nothing here indicates we should only be getting one user's images so the change we have to make here is again somewhat obvious we need to make sure this function only Returns the images that this current user who signed in now should have access to in order to do that we have to decide where we actually get the user ID there's a couple different ways we can do that we could fetch the ID in here and pass it so we' have like user ID string that gets passed or we can actually do the authentication in here personally I'm fine just doing the off call in here but it's not too big a deal either way just ideally you pick one pattern and stick with it or you have a consistent wrap pattern where you always have the off calls in the same place otherwise it gets messy fast personally the way I would do this is I would rename the function get my images where my is the current user const user equals off if no user ID thrown this error unauthorized and now we know this is the case and we can also do a wear check here model EQ Tada good old auto complete what this is saying is effectively that we want to select all of the images from our database where the user ID is the same as the one that we have here so model. user ID this is the one for the reference for the images that we're querying and then user ID here is the one that we're actually getting from our off call so this should make sure we're only getting the images that this user should be able to see I think it's worth quickly testing this or I have to rename this first so I renamed the function but now this all seems to work as expected only one way to test it though hop back over open our project up and we can still see all of the images but that's cuz I was using this one account what I need to do now is switch accounts previously I was signing with GitHub I want to test a different account so this time I'm going to be signing with my Google account we go sign in we go to Google continue and now we're back here and I don't see any images I don't see any images because I don't have any images it's a different account if I was to comment out these two lines and go back here I have all the images again because they were uploaded on a different account by doing those additional checks and these additional bits of code here we are now certain that there is no way a user will see images that don't belong to them and as long as we're not calling DB queries in other places it should be relatively easy for us to look through this one file and make sure all the places that return images are properly authenticated once you want to impl things like delete this gets even more important so we haven't formally used react taint and I honestly don't think we'll need to for this project the reason you would use taint is if you have some data you don't want to return to the user so if I was passing props to something that was like a client component and I wanted to make sure we didn't have data like in this user object we have stuff like user do sessions or user. token or get token if we called this yeah to might not be the best example here let's say user do do they have any metadata okay this might not be the best example because this doesn't have data that we don't want on it but let's say when we fetched the images from database that there was some field we didn't want want like let's pretend we don't actually want the URL to get to the users what taint would do is allow us to wrap the specific keys inside of this object to tell the react compiler hey never ever ever ever send this to users this is useful when you have things like passwords in your database or tokens for your different users in your off calls and stuff like that the blog post has a much better job of explaining this than I do still experimental I believe but the point of taint is that you can tell the react compiler hey by the way we don't want this part to ever go to the client so if we have data and then we mark it as a tainted object reference this cannot go to the user anymore theoretically you could still dump this object have a new object and return that but that's a much less likely thing somebody would do the goal here isn't to be a perfect guaranteed this data will never leak so much as a method to make it easier to determine within your code base and communicate to the team as well as the compiler hey by the way this doesn't go to the user this is kind of like the example talking about where we have user data but we don't want them to ever send the token we might be cool with them sending data so we could even delete the taint object reference but if we specifically don't want the token key from this object to go we could just do this part instead very very useful if we find a place to sneak that in we will but the core thing I wanted to to Showcase here is the idea of having a separate folder where all of your backend data stuff both Getters and modifiers so your queries as well as your mutations having this in one place does make it a lot easier and having a separate folder means in the future you can add custom lint rules and stuff like that too so if you're making a real code base so to speak I think you should separate out like this sooner rather than later and I'll almost certainly break this rule as we go so make sure you make fun of me in the comments for that as well let's get this committed queries into separate file cool now we've done that and we've handled off for the actual file access we got more fun stuff to do here next image will be an interesting change because right now I'm not really doing much in terms of handling widths image isn't going to help much though if you're not already familiar with the nextjs image component it's honestly kind of magical I was really skeptical at first but the more of used it the more I understand why it's so powerful the magic of the next image component isn't like oh they reinvented the image tag it's that the magic of this component isn't like oh it's just a better IMG component it's much deeper than that the value of this component isn't even on the client side it's mostly on the server side what's really cool here is that this component will take the images that you pass to the source tag and when a user fetches it depending on how much resolution it needs for their use case they'll optimize a perfectly minified image for that user so you can have Source data that's massive images and sharp on the server side will make sure you don't send a picture bigger than the user actually needs this is super important and if you look at something like image. t3g a lot of the images I have here are super highres but the version on the site isn't and if I zoom in you'll see it's actually a little bit pixelated that's not something I coded or set myself that's just the default Behavior with next image because at a normal resolution you won't notice but do I command plus this a bunch it'll actually start fetching higher res versions automatically based on how much space the image is actually taking up on my screen literal magic this stuff is it makes a project like this actually viable rather than having to optimize and make 15 versions of every image yourself this just generates them when the user actually needs them obviously this does cost money once you hit a certain amount of usage on versell so if we go to versell pricing image optimization I don't want you guys to think I'm rug pulling you on the hobby tier you get a th000 images per month of optimization for free on the pro tier you get up to 5,000 for free and then it's not free if you're paying the monthly fee but then you get another five or th000 images optimized for $5 you can use other services if you're interested I believe Cloud Nary is a little bit cheaper but honestly it's pretty hard to beat the built-in stuff here so I'm just going to use that so first and foremost we need to switch this to image we'll notice that there are some props that are unhappy the easiest way to fix that because again like this component needs to be able to know some amount of how big it's going to be in order for it to get the right image size for your display so you can't just drop it and let the container figured out unless you literally tell to do that which we'll do here by putting fill actually they they changed the right thing to do here so we'll change this quickly to uh what is it it's um object fit I believe and we also need an ALT tag which honestly I'm happy they made this required because it should be required well now we have object fit is fill alt is image name and this is the error I was expecting well one of the few errors I would be expecting missing the required with property that means that the object fit fill was not the right value if we go to the docs for this I can just command F4 fill or just scroll a little bit oh it's style object fit now they changed it I forgot about that I knew they had changed it forgot what they had changed it to contains probably the right name for that because we wanted to make sure it uses the right amount of the parent container now if we go back here and then might just need fill as well I was so sure I had that right the first time ah here's the other error that I was trying to get this error is a bit different now that we have told the component hey by the way fill the space you have and make the sure the image contains and fits within it we need to tell the next config hey this is one of the URLs were cool with optimizing images from if you don't do this then theoretically anyone could throw any image from any Source into your app and you would just blindly optimize it which could potentially be a security issue it could be really expensive it could result in people abusing your service so we want to specify only upload thing URLs are allowed to be optimized actually telling nextjs to optimize for our URL which is in this case utfs is not too annoying I don't love the syntax but that's totally fine we have an images key it's a remote patterns is the new name for this array this has to be an array the actual stuff you care about in this case host name utfs doio and that's all we need to do here now those images will be optimizable nextjs is now aware that we are cool with allowing images that come from upload thing with being used we run Dev again CU I accidentally killed it earlier and now this loaded you'll notice uh the pictures are uh not quite taking up the right amount of space you should probably figure out what's going on there I have a couple guesses if I comment out object F contain how do we do that nope seems like the width here isn't enough that we also have to have a height still looks like it is not happy placing this image inside of there there is couple easy fixes can't have fill and a minim or manually set with like that Tada look at that not bad at all probably a little closer to the pixel value here I'll just do the 192 just make it a little closer to matching uh that's a little bit Jank when it like pops in and isn't quite sure be nice to be able to fix that honestly the best way to fix that would be to have like a fixed size that all of these boxes are so that you always have the same height even if certain elements are taller or shorter so if I was to add one taller picture here it would change the height of the whole row right now which is not ideal this is a totally fine starting point but there's a lot of little polish we can do here one other thing that's kind of driving me mad right now it's not centered there we go that makes me feel so much better cool but now we're using the image component now all these images are off optimized and if we scroll in zoom in you'll see it's a lot more blurry which might seem like a bad thing but since when we're zoomed out it's totally fine this is actually a great thing because the amount of data the client needs to fetch to use these images is significantly less and also if we take a look at the source here if we inspect and look at the image we'll see the URL forward is no longer just utfs it's some crazy stuff with _ nextext SL image question mark with the URL parameter the reason for that is this is allowing our server to make image on the fly now that we have that done we are one more step completed next up we're add in Sentry I want to commit the changes use next image push that now I set up erroring because as you guys saw that time I ran into quite a few errors and if we run into those in production it'd be really nice to know the beauty of something like Sentry is that it's surprisingly easy to set up and you get awesome feedback when errors happen in your application the setup is much better than it used to be making something as important as this work well that integrates on every piece of your front end and your back end is difficult and they put a lot of time into making Sentry a great solution for this there is a tiny bit of bad news which I'll just jump in front of now which is that at the time of recording turbo is not compatible with Sentry which only matters in Dev so I'm temporarily deleting the D- turbo in the dev command so that when we do this it will actually work when we're testing it locally We'll add the turbo command back after because we don't care if we get errors locally we only care in production and production builds don't use turbo yet but for Dev I like using turbo so let's just turn that off for now hop in here we see an old project that I was testing with before but that's not the one I wanted to care about so if you're already signed in and set up with Sentry pretty easy to sign in and make an account go get that done where things start to matter is we want to create a new project so we go here they even have a fancy little nextjs button click that create project this is also an important thing to set this is a side project I don't want them to spam you with stuff so I'm just going to click I'll create my own alerts later if you want them to spam me which could be really nice as like a page Duty type thing really need to have that you also can have a threshold for how many errors you want to occur before you get notified so if you have a popular service and you want to make sure like hey if five people get this error let me know so you can say occurrences so if this error happens 10 times notify me or if 10 users are affected that's such a nice toggle these are the little things that like when Sentry's been around for so long that they're able to build and do and I'm really pumped they were willing to work with us to make this all possible so let's do uh T3 Gallery video you can name this whatever you want I'm just naming it that because it'll help me keep track of it now we're going to run the Sentry wizard this is so cool it's so cool that I actually plan on stealing it and copying it for upload thing because of just how how much easier it makes doing something like this we're just going to copy and paste the command MPX Sentry wizard at latest you have uncommitted or untracked we do get status oh yeah the turbo change get commit gem temp disable turbo cool paste that now how do we want to use Sentry another one of the cool parts of Sentry is that the code is all out there for you to use you can set up the Sentry back yourself if you want to it's not open source and the traditional like you can go Fork it and sell it yourself sense but they are using the functional software license the FSL which theoretically should allow you to use all of their source code if you're just willing to wait 2 years from when the specific commit you want to use was made so you can host this yourself and have a great experience but obviously I'm lazy we'll be using Sentry directly do we already have a Sentry account yes and here it goes straight to your browser authenticates for you tells you to return to your terminal that's so nice pick the project you want to link here now we're installing do you want to create an example page yes again very handy their whole thing is they're tracking errors for us so wouldn't it be nice if they showed us a page where we can force an error and actually make sure it all works another cool part here are using a cicd tool we probably should have set up GitHub actions before this but uh we didn't here's an environment variable token I'm just going to grab this now because it's easier to have it now rather than deal with it later so hop down here hop into EnV paste save now we have that for what we need it later uh do not commit to the repository yet we know that cool now this is all done we have next Dev and the Sentry example page all set up properly theoretically fingers crossed this should all work as expected so now if we rerun our project CU that got killed while we were doing that PPM Dev what is it m about oh this is one of the other annoying things that they do it's a somewhat easy fix just annoying so we've already been working inside of our next config and we're using esm syntax where we're doing proper traditional imports their expectation is that you're still using a CJs or old school next config so they use require in module exports this is somewhat easy to fix but we have to actually do it so Above This export we already have our config that we made but we need to use these new things so import with Sentry config from Sentry nextjs and here we want to make the new config so I could name this core config so we know this is like the top level like these are the things that we defined ourselves but now we want to have the actual main config be the thing that's wrapped so with centry config in here is where you put all the other values I'll just do that for now we'll hop down here and I'm going to grab all of this delete the rest and now I'm going to paste that right here cool so now we have all of the config that they added just using the different syntax no more requires still exporting it as a default this all should work as expected there's a couple other really cool things they snuck in here like tunnel routes the value here is that certain providers of things like analytics and like error handling tend to be blocked by ad blockers a lot of companies that make ad blockers well I shouldn't say companies a lot of developers who make ad blockers aren't just against ads they're against tracking as a whole and it's not uncommon for them to block anything that resembles tracking be it error reporting be it product analytics be it a feedback form they'll just block the things they go to and they do that by blocking the whole domain like Sentry or like post hog which we'll get to later the tunnel route is a really handy trick where it will run the traffic through SL monitoring on your URL and then on your server it's going to rewrite this traffic to point somewhere else in this case Sentry so it's much harder to add block these types of things because they exist on your domain so you can't block it via URL you have to block it via path and if you use this URL for anything else it all falls apart so really handy that you can do something like this to make sure you still get your errors even if a user is using an ad block so that we have all this set up we actually go see it so let's pnpm Dev homepage still works that's not the page we want to test here the page we wanted to test is our new century example error page. jsx also gross. jsx we we'll fix these and make them TSX in a minute just needed to get this started it's already mted as for not having a typescript file let's shut up copilot and we see in the here we have a basic Sentry onboarding y yada the important piece is down here we have a button where we onclick Sentry do start span This Is Us wrapping our thing that we're going to do which is in this case is a fetch so that if an error happens within here it gets caught as part of this Sentry event so here we throw the new error centry example front end error because we're fetching from an endpoint that doesn't exist we never made SL API centry example API if we did this would pass but since we we're going to throw an error so if we go to this URL which is Sentry example page so just SL Sentry D example- page we'll see this fancy fun new page where we can throw an error and we see we got this error here but I don't care about the error here what I care about is the error here if we hop back to the T3 Gallery project you'll see error Sentry example front end error this is an error that just happened 13 seconds ago because we clicked that button how cool is that that with like two terminal commands and a little bit of moving things around in a config we now have the ability for all the errors in our app to get hydrated up to Sentry it makes debugging significantly easier they also will track source mounts since we set up build so will show you the exact line of code in the exact place in your code base which is just mind-blowing it is so cool to have it at that level and once you've linked it to GitHub you can click a button and it will bring you straight to that thing inside of GitHub so cool they even have little replays that they'll show you so here's the replay of what the user experienced and you can see they click the button they get the error also all the text is censored by default which is really nice it makes it much less likely that you get personal information that you shouldn't have access to but we can see what they did and the error that they got as well as a whole stack trace of all the things that happened there pretty nice I know that I slept on Sentry for a long time we ended up needing it for a bunch of the stuff that we've been building lately and all the complex user experiences that we've been working on at ping having a tool like this is essential and I'm really pumped that we now are working with the best one so that was hilariously easy I'm going to delete the centry example page cuz I don't need that anymore but I do want to tidy up the global error a tiny bit because I don't want a non-ts file in my code base I'm sure you understand so we're going to rename this to TSX but now we're getting type errors because we don't know what error is or really anything about this just yet so I have a slight change I don't love the syntax of just having your objects dumped in your function it's fine just not my thing so instead we're going to do props which has the type error uncore unknown because we don't know what the error is so calling it unknown is the most realistic way to describe it now we need to change these two calls so that we're not just calling error directly it's props error and here here we're actually seeing a type error we weren't getting before which is that the error component from next has specific expectations that aren't being met so it is missing status code so ideally we would know based on the specific error we got what type of status code to throw we could throw like a four or four we could throw a 500 there's a lot of different things we can throw all let the auto complete do its thing status code 500 title error cool now we got the default nextjs error view with the status code and the title and we are capturing this error and sending it to Sentry before we do anything else so this makes it much more likely that we actually get the error data when users have issues and as I mentioned before this is not being blocked because of the fancy stuff we did there with the rewrites so you're basically guaranteed to get that error data when a user has an issue I think this is really cool so with that all said we are now done setting up our error management and now we get to go cause a lot of Errors because this parallel routing stuff is both incredible and kind of hard to do I'm going to demo it first and then we're going to build it next.js parallel routes look at that I already have the docs here I know they have an example I'm going to lean on ler rob a bit because te made a really nice example showcasing just how cool these parallel routes are I'll be sure link this Linked In the description so right now we're just on the basic next gram versell app eurl when I click one of these it opens in a modal which pretty typical Behavior the way that Instagram Twitter all these other platforms work except the URL changed so what happens when I refresh this is where this pattern gets really cool because it works just like Twitter this is its own page that way I can send this to somebody and it works or I can refresh or close my browser and open it this will behave with an actual useful page because technically if we had made like I don't know 100 other images and two wasn't visible here anymore and that URL was expected to go to the homepage with this as a modal that might not work but this also lets us have the good product experience of clicking it and having it come up on top of our current context without having to go to another place it's a kind of Niche pattern but it's a niche pattern that I've seen used in so much that it makes a lot of sense for a project like this so we're going to be borrowing a lot of the cool things that Lee did here all the source code is available because of course it is it's meant to be a demo and we'll be borrowing a decent bit from it so the first thing we need to deal with is this at modal thing this might seem confusing and it kind of is it's one of those weird things where we're putting things in folder names and those magically become accessible values in next and I also want to be clear this is more of a next thing than it is a react thing so other Frameworks will have their own weird equivalent of this react does not prescribe anything for this it's a cool thing that the op router added so if we go to the pages here we'll see the page is kind of boring we just have link the ID stuff like that the modal thing is more important in the layout you'll see here that the root layout doesn't just have children anymore it also has modal and this kind of puts it in the body here we can just copy this for now so I'll grab that hop over to our code base go to layout remember we're in the layout right now paste that CU it's an additional value modal here and now if we want to use this we can just put it right below children children and modal nice and easy the modal Roots also a really nice thing to have which uh I'll sneak in here it gives you a thing to Target and mount elements so this will make life quite a bit easier as we get to that point but now we actually need to make that modal directory in order for this to do anything what's it mad about with modal here modal is not a valid layout prop you're bullshitting root layout props children modal oh yeah the next type definitions are really smart so if I make a folder at modal that enough to get to shut up it is yes it is how cool is that I just made a new folder and it fixed the type definitions they are getting better about this I need to stop talking as much as I do cool so first thing we need in the model is a default State I know you don't really need defaults in much of the other stuff here but the point of the default is to allow for next to always render whatever the at modal is even if you don't have content for it so in here we're going to do default TSX export default function default model return null and now that we have a default we can actually get to work so let's hop over here take a look at the rest of the modal code we have photos slid I will not pretend this is a good folder name but this is app router being app router this is a bit of why I'm not the biggest fan of doing everything through file names but the the power of what this enables is honestly incredible so we have the do photos we have the SL ET ID folder the use of the brackets here is to indicate to next that the thing between the brackets is now a variable that we have access to so if we have the page. TSX here this is just rendering a modal which is in the directory right there just yet we're just going to copy the page TSX for now page. TSX paste paste know the modal so I'm going to change this to just a div for now and we have the ID all cool one other thing I don't love this being named photo so I am going to rename this just to image like IMG I think it's a better name we we also need H I don't think we actually need anything else do we need a default up top no we don't cool we now have all of the essential pieces for this to render we actually need to get it to render so what do we do for that again here's where the magic of the new next stuff really kicks in we wrap this with a link tag we'll use the actual next link because then it will route on the client side link cool that's wrong but we can work with it and I can put the other side of this link tag below the important piece here is that we are just going straight to SL image/ image. and in here we're just going to render the ID below so theoretically when we click one of these it should add a new element to the page that has that ID in it so let's go see if this works hop over to the app we'll click one of these images and if it works all proper then we should have the ID which in this case is 10 appear below the current page content so we click here a 10 appears click here a seven appears this might seem trivial but it is really cool that we're on the same page even though the URL changed if we refresh it'll be a different content except we've actually made the page for that yet so we can do that really quick for now the easiest thing to do is I'm just going to copy the image ID there we'll paste it and we don't want the dot because we don't want to tell it like hey go up an element which is what we were doing before CU we wanted it to escape the at modal container so if we delete that it'll no longer be doing that we have image ID page same content for now totally fine it's just going to render the photo ID if we refresh it's just8 we go back to the homepage and we have all the page content again I click one of these it shows up there and if I refresh becomes a full page I have to go back this all navigates and behaves as you would expect I think this is really cool I'm so pumped that this is part of next even if I don't love the Syntax for some of these folder names the ability to define a a key with that at syntax that is now accessible in the layout lets you do some really cool stuff another way to think of this is when you have a page TSX that becomes the children of the current layout when you have something like at modal or chat help or whatever else you want to have like this whatever is in here goes in the modal tag or whatever other tag that you have this let you define custom alternatives to children that the layout has access to but now we want to actually do something with this because right now we're just rendering an ID first we need to actually get the info for the image so we need to hop back over to our query file queries. TS and we need to export a function to handle this oh look at that that was some really smart Auto completing why is this mad about the EQ because ID is not a string it's a number that is why cool I'll explain this line by line cuz me just Auto completing the code and forcing you to type it's no fun we are awaiting db. query. images. find first because we only want one image this time we don't want to find all of the images we want to find one we want to find the one where using model is like the comparison EQ where the model ID is equal to the ID that we passed here if no image throw new error return image if we really want to to we could do an additional off check here and also make sure that the not only are the IDS for the image equal but the users allowed to access this thing it's actually another fun thing we can do here for that we'll just do this cuz again everything should be safe we have the user we have the image we have if there's no image we throw if there's no user ID we throw honestly we should do this check first so that we don't have to worry about it cool and if there's no image we throw so now we know there's a user and there's an image so our last check is if look at that if image. user ID does not equal to user user ID that we throw an error unauthorized and now we just return the image so as long as all of that code Works which simple enough it probably should we can hop into we'll start with the model but remember we have to write this code in two places because we want this to render both on the page as well as in the modal that we're going to have up here so in here actually need the image make this async const image equals a wait get image import that this is pam. photo ID oh no we don't even have we just have photo ID here sadly photo ID is a string because we're getting it from the URL so we need to turn this into a number so const ID as num that's fine and we can make sure it's not Nan drop that here and now we have the image I'm going to be lazy and just poorly render this as an image tag with a source and we'll do sty or I'll class name CU I'm lazy equals w96 I think pretty big yeah we'll go with 96 for now and now when I click one of these a giant image appears below we don't want it below we want it to be a modal but as a starting point this is really good hopefully this pattern is starting to click cuz it honestly took me a little bit so don't feel bad if this is a little harder to follow than the rest of the tutorial so far there's a lot of weirdness to this and if you really want to understand it check out the full parallel routing docs in the next docs if you really don't like this or you don't care for the URL patterns I was showing earlier you can also do this with a state management Library like zustand and just render things when they're in your state I actually did that for a version I was working on before it was totally fine but I think this pattern is cool enough that I wanted to show it off so I think this is important enough progress that I'm choosing to commit it now so if I was working on a real project I certainly would have made a branch for this feature and committed to that first did I forget to add Sentry via get first I am so silly that is a little bit annoying I'm going to go out of my way to this was a dumb mistake this is why been committing all the time I absolutely should have committed the Sentry stuff first and then done this later if I was being perfect here I would manually stash or go temporarily remove all of the things I changed for the layout stuff but I just want this tutorial to be good so uh we're just going to commit this all together get add- a get commit DM I want to be very clear in this commit message that I accidentally did two things so add Sentry and start implementing parallel routes typing is hard okay now that's committed nice I actually spent quite a of time trying to hunt for a nice minimal modal component and I was a little horrified at how bad most of them were so instead of fighting all that I'm going to start with the one Lee made here and we'll make some slight adjustments as we go but it's a good enough starting point so I'm again in the next gram code base app at modal photos ID model. TSX I'm just going to click the RAW button copy technically I think I could have clicked this button to copy the content I'll do that instead same difference we going go back here for now we just want the model here so I don't care to separate it into to another place if we wanted to be proper and we also wanted to use modal other places I'd put it in a components folder higher up model. TSX paste save let's just test this as is and see what it looks like it's not going to look good but at least it will look paste import save go back to our app click something look at that we have a nice little modal of our image right in the middle of the page that was hilariously easy he even bound the escape button for us so I'm just pressing the Escape key to close these things and it really is that easy to make them go away this is a good starting point but we want it to look better so let's get this customized before we do that though brief story time you might have noticed that I look a little different that's because I'm recording this for like the fourth or fifth time now a few weeks later the reason for that is because this kind of sucked I'd previously sketched out this project and I had a fix working for the dialogue to make it full screen properly but I lost it and the result was chaos trying to debug I have a whole video about this that we already published but I don't want to waste time on the details I just want to show you the problem and how I solved it really quick so let's hop over to our actual modal so open this up again and you'll see in here that we have the class modal backdrop class modal I want the dialogue to be the main element and I want to be able to fill it up with like things inside of it so let's make some quick changes so we can do that we're going to just delete that outer layer because we don't need it the dialogue is our top layer modal isn't really a thing so we don't need to give that that class name let's just give it w screen H screen and a color so we know that this is going right so now when we go back here and we click something we'll see it's here but it's not quite going to the edges it has some space around the dialogue if we quickly inspect and we select the element you'll see it has this margin margin 19 across everything so let's just get rid of that m0 nope it just shifts it up to the side so it's now properly top left but now it has this 38 pixel space on the bottom in the right and no indication of a margin or border anywhere in here so what the hell is going on well if we scroll down here you'll see there's a Max height and width set that are really strange if we go to where they come from Max withth and Max height are set as a default Style by the browser at calc 100% minus 2 emus 6 pixels just real bad so the best way to fix that honestly is to just throw it in the global Styles I hate doing this but it's a part of the reset that didn't get included effectively so we're going to select all dialogues and on dialogues we're going to set max width to 100 VW and Max height to 100 VH perfect look at how smart that auto complete is and now if we open this up again it's properly full screen now nice right now we can actually get styling this so first off we don't want that to be BG red 200 let's give this a nice gray let's do BG zinc 900 over 50 the slash syntax there is to add in like opacity so this is 50% opacity now which actually looks pretty nice this is obviously in the wrong place when we have a lot of work to do here but it's a good starting point but now when I go to one of these and I refresh this is entirely different from the page that we just were working on we want to make that reusable so we need to make a place to put reusable components we have the underscore components here but as I mentioned I like to use these for things that are specific to the route just to keep the routing clear and since the top nav only is used in layout I like it being there but if we want something that's going to be reused across the code base that's when making a comp components folder starts to make a lot of sense so let's do components SL full image page. TSX and for now I'm just going to yank the contents out of page TSX and make some quick changes obviously we don't need this to be in the model because not every instance of it is going to have a model and honestly this shouldn't have pams because pams are one that's part of the route and this isn't part of the route so I'm going to do my preferred definition here which is props im or we'll still call it photo ID string it's not going to be a string though it's going to be a number because these are number IDs so Props is a photo ID of type number you can even change that to just be ID since it's the full page image view this is the full page image view when you pass an ID it's assumed that's going to be an ID for this so I don't think we need to specify any further but here we go it's that simple now we just await the image and then we return it need make sure we're actually using this though so let's hop back to our page TSX here we still want to render the modal remember we're going to change this to be full page image view id id is number and we don't need to do this await here which means we don't need this a Sync here either kill all of that and this should be reusable and look the exact same as it did before cool that looks the same but now if I grab this guy in fact I can just grab all of the contents here we hop over to the image route page TSX remember we want these to be as similar as possible effectively ween name this from photo model to photo page delete the modal because we don't want to use that and now this just renders the full page image view directly so now when I click this it'll render it up there and when I refresh it renders that as the page content on this page great stuff I think this would be worth committing so I'm going to remember how I said I was re-recording ignore that let's add that commit this as fixed dialogue full screen made component for image view fantastic so let's actually work on the layout of this now what I'm thinking is we'll have the image on the left side and then the like info about it on the right side if you ever seen a image selected full screen on Twitter this should be pretty similar this is what I mean by the way so on Twitter when I select an image and I have it open you have the image on the left side here and you have the side on the right that has the info in this case the info is my tweet but for our app the info is going to be the name of the image when it was uploaded who uploaded it all these other types of things so let's get to work on getting that all set up quick Pro tip in case anyone somehow doesn't know it if you command click a component or anything like that that's imported it'll bring you to where it came from which makes it really easy top around things like that so now that we're here we need a lot more than just an image tag I will advise using the standard IMG image instead of next image here because next image does a lot of like preloading with fake elements in order to try and take up the right amount of space we don't even know the size of the image because the user uploaded it so it's a it's a tough Catch 22 with trying to implement the next image component the right way here so I would personally recommend just using the IMG tag if anybody wants to file a PO request on the repo that makes this work properly with next image bonus points for that so let's start working on this layout obviously we want to wrap this in a div because now we have a place to actually control the layout so this should obviously be a flex love that auto complete but it's not quite what I'm looking for so we're going to type this one out ourselves class name equals Flex WF and honestly since this is going to be a bit complex let's give this a background just to know that it's working how we expect it to so now that we have the BG green we can see it's taking up the right horizontal amount but not the right vertical amount because we didn't do HF again certain things don't hot reload right oh I guess that was hot reloading right it's just stretching it weird so we need to give the image a bit more information because the flex box is causing it to to vertically Flex take up all of its space object contain should fix that yeah object contain will fix that so the image won't get bigger than it's supposed to I am seeing that we're able to scroll a bit though if I recall there was something in the modal that we never applied CSS to that I think we should probably just get rid of so if we hop in here we'll see the uh delete button or close button I'm just going to get rid of that guy for now to make sure that yep that fixes the scroll Behavior because we were defining the sizing like the full width and height in the child here so the button would have to be somehow made accessible other ways honestly we'll figure this out later and right now just pressing the Escape key is fine but problem for later so now we have this this looks correct and when I refresh we go here where it is not quite correct think that might actually require a page level layout change but won't be too bad to figure it out so now we should actually work on the layout of this full page image component so we want to have that side nav so we'll div honestly the width should probably be on this because we would want the side to have a specific width rather than the image the should take up whatever space is available this should just be a specific size one of my favorite tailin things is to just pick a number that you think feels and looks about right and then adjust up or down until you find the one that's the right fit Tailwind has a default spacing scale that they use for pretty much everything where you only have access to certain numbers you'll notice up to here it's pretty consistent but then we skip 13 then we skip 15 then we skip 17 18 19 the point here is to make a system where you're using the same general padding sets so everything fits together much better better and honestly the spaces between these things are rarely needed I should say needed this is more like a design decision that was made with tail and it's one of the few things that makes it less just CSS and more a library that has a design system within it but I like it a lot because it lets you just use these different names that map to the Sizer pixels and over time I've stopped thinking in terms of the one remem or 16 pixels and I just think in the four or the eight of Tailwind which is really nice and I find myself going to this page to figure out which spacing I'm using more often than I would like to admit but obviously this all exists in the tool tip here too you can see with 12 REM it's there you have all this information right where you need it so we'll start with with 48 this also should be a flex and a flex column specifically because we want this to be vertical column and in here we can start applying other things well that's pretty bold of them to assume that I want text Excel font bold for this and the image name that is correct though so here we now have the image name and all that additional information let's see if that rendered correctly hop over here look at that Apple USA sadly I set the width here but it's still taking up a lot of space partially because we left the width 96 there but now it's taking up too much space there's a a couple reasons for that this is the wonderful minwidth chaos if we set Min w0 there and we set uh Flex shrink here now it will shrink accordingly and if we go back we show should have why not not shrink accordingly you know it is partially working because the green doesn't go any further because we have that set here with the w full H full Min with zero but this guy is not flexing properly honestly when I run into things like this usually I'll class name equals Flex shrink on a div on the outside because I find divs to behave better than images in most cases and we're probably going to want to do more things here anyways and then on this side Flex shrink zero to make it clear that we never want that a shrink and look at that we're now good and in both layouts too and here it works and when I refresh to go to this layout it works as well you might previously have noticed how often I was refreshing when I made a change the reason for that was the next version I recorded most of the tutorial in had a annoying bug where hot reloading was broken where I would make a change and save and if I had one of these like parallel routes it would switched the one I was on to the other type the bug has since been fixed you might notice it for the rest of the tutorial in fact I did a little blur about it but for the sake of the part that I'm re-recording here I did actually just bump the next version up to the latest which at this point is 1421 and that bug went away which has made working in this Indie bugging it much easier I'm actually thankful that all of you won't have that problem when I because writing this with that bug was very very annoying thanks to the next team for getting that one fixed anyways we probably don't need that background green anymore and if I delete that and save it you'll see we stay in the right route we might also have noticed that our Global Styles aren't applying because the dialogue element exists outside of where those styles are being applied if we go back to our Global layout you'll see that we're putting font Sands and all these other things in the body and if we go to our default Styles we're applying to the body BG black text white sadly this doesn't apply to the dialogue dialogue has its text reset so we could do the simple thing here or we could go apply it in the modal there's a lot of places to put it honestly I think for the sake of clarity especially since in here we're also setting the BG color text white probably best put there and there we go the text is now white for Apple USA and when I refresh it still brings us to this route cool stuff there's a lot of little things here I want to fix first I want to make sure this image is down Center so it's like in the middle properly rather than like push all the way up to the top I want to add some borders on the side here I want to add additional metadata here as well all things that are relatively easy to do let's get started with those changes so pretty much done with the modal won't have to touch that for a while at least we have more work to do in here as I was saying I want to make the center to the middle here there's a couple ways to do that this would have to be a flex box for that to work so Flex justify center items Center that should do it yeah look at that we now have the image nice and centered I still want that side nav though so let's do that well the side border borders are so nice and simple to do in Tailwind just border Dash l so we need to fix the fact this isn't taking up the vertical height it will make debugging and working on the rest of this going forward much easier and also I haven't shown this yet but if we you were to fill the homepage with more stuff it would break the way the layout Works a bit what I want is for this to scroll without the whole page scrolling so we can have the top NAB on top and have all of this stuff in the rest of the page but I also want this to be full screen we could Brute Force this with some crazy flexbox hacks but honestly this is when grid comes in really handy so I'm going to commit what we have now first off so get status get add P you see I still have the next change in here so I'm just going to get add those first package.json pmpm lockl cool so those are added let's pick for the rest fix the text fix that fix the Border more clean up on modal cool now that we have that done let's fix the root layout first we want to delete all of the flex stuff because we're not going to be using Flex here anymore next this is just a general pro tip having a separate div that includes all of these things makes life much easier I've had some weird things where certain JavaScript libraries try to drop stuff in body and if you have a grid set up it can break the whole page layout so doing all of this in a div that's full size makes this much easier I've also chosen to keep the modal outside of said div just to again keep it from being part of the core layout here's where we have to actually apply the classes equals first we want this to be H screen because it's going to be the full screen space and nothing more or less we want to actually Define grid and we're going to give it the config which we want a conf configure the rows and this is being Auto completed correctly because it's a pretty common config of Auto and then one fr the syntax is weird I know but I promise you normal grid syntax is even weirder the simplest way of explaining this is we have two rows one the first row is auto which means whatever space it decides to take up it's going to take up the next section is 1fr which stands for one fraction if we had multiple of these like I did 1R 1R 2fr then what would happen here is would whichever of these commands a height would become one fraction height so if this one has a height of 20 set and none of the others do then this will now also have a height of 20 and then the two ofar one will have a height of 40 so this is the configuration within a grid highly recommend like the CSS tricks how grid Works article if you want to learn more about this but for now we want the first thing to just Auto scale because we set a height in topnav then we want the second thing which is the rest of the page to be 1fr which is one fraction now we want to actually give this the stuff that it should have which in all of our Pages we'd want this to be main so we'll wrap this with the main tag quick we also need to handle the scrolling case so we can do that here too with class name equals overflow y scroll so now if we did this all correctly and we go back to our homepage that all looks good let's quickly make it so there's way more images so we can more meaningfully test this to my favorite trick and now go back here we have that and the top stays while the bottom Scrolls and if we did this right and we go to the page it's now the proper full height Tada great is confusing I'm not going to sit here and pretend otherwise but when it works it's so nice and I hope this gives you a little bit more confidence with it one last little thing I see is that this is a little close to the top that same padding was the padding that was causing the Gap in this page so I want to handle that but still have padding here quickest way to fix that is to hop into our page and instead of just the gap for The Gap applies the things inside of this container let's do pt H we want padding on the bottom too we want padding on every direction theoretically if the images were constrained on the sides that'd be bad so we'll just give this P4 for everything and now we scroll the bottom there's a little more wiggle room and there's room on the left and right if it was getting really close to the edges that's better in every way I can think of so let's absolutely commit that get commitm move to grid cool let's get back to fixing that layout up with 48's a bit small for all the things we're going to be including here and the text Excel is honestly a little bit big let's go back over to full page image full image page I'm going to close all my other tabs because this the only one I'm really working in right now change this to text LG I actually love the font bold either so let's kill that let's give it a border on the bottom so that it has its own little section and text Center so the text is in the Middle look at that I'll give it a bit of padding we'll do P2 look at that doesn't that look nice super simple clean clear I'm happy with that so far let's get a bit more info in here so this can look nice and clean and actually be useful so we want this all in the same Flex for the column on the right side but we want it to be separate going to be real lazy here and put a gap we'll just do Gap two for now now let's make these additional elements we'll do div uh up loed by it would probably be easier to have this be its own little contained bit so I'll do that class name equals Flex Flex call span so we're going to want to style these differently almost certainly but I also just realized we don't know who this was uploaded by we don't have their info or do we we have the image and we know what we have from here if I look here we have the user ID for the image so let's get more info on them and remember this might not be the user who's currently signed in if you decided to allow anyone to go to a specific images page if you share the image or share I don't know make an album in the future we need to know that we actually have the info for the person that did the upload not the one who's looking at it so we can't just call off we have to call clerk client const uploader info equals await clerk client. users. getet user and here have to pass the the ID so image. user ID and now we have uploader info and I can use that so they might have a name they have a full name though so we'll just use that uploaded by Theo Brown nice we need padding though eh I'm going to be lazy with it px2 that gives it enough room perfect and if I copy paste that yeah that is enough space I'd like there to be a little more space between these and honestly we could get rid of the Gap if we did it too so instead of px2 we'll just change it to P2 and I'm going to get rid of the Gap to keep the top spacing the same nice that actually looks good obviously we don't to put uploaded by twice so uh we'll do uh I guess created on also don't love the Callin honestly so we're going to get rid of that too so let's make a nice formatted created on temporal hasn't shipped yet we can let autocomplete do its thing because new date image. created. loal string nice I don't really want the whole date time so what'll do to to local date string is the one I was looking for there we go created on that uploaded by Theo nice going around to the colons let's put them back let's take a look at how this looks on the homepage too looks like it's hard to see the text we do have like only 90 or 50 opacity I think so we probably want to make that background darker which if you remember we did in the modal yeah we're at zinc 950 honestly the gray and everything else is black it's not going to look great so I'm thinking we switch back to black and we put a much much more strict opacity change there I did notice that didn't the hot reloading is a little bit weird but this looks hilariously better I'm feeling pretty good about this now it all looks right it all works as I would expect let's finalize these changes let's go into our readme and check this as done pretty hyped that we can finally do that go back over here get status get add add that the BG black change get the user info and the new rendering um finalize modal and parallel image routes check that out I should probably go kill the repeat of the data I have on the homepage nice and once again this just works I should handle the padding for this some amount and honestly this element should have padding just to make it so it's not always pushed up against the edges if the page is the right size yeah look at that I'll commit that it's a small dumb thing but why not add P add get commit add padding back on homepage and I'm going to get push this just so that we can get a fresh build up make sure everything's working I'm I'm sure it will be but just want to confirm that Al technically don't need this anymore because we're doing things on the route that are Dynamic and it knows such but it is not a bad call when you need this page to load every time for sure to throw this on the page just to be sure so let's fix that upload button since this is only going to be used in one place I could throw it in here and honestly I'm going to do that I'll call this uh simple upload button the current upload thing component is focused on making your upload Button as easy as possible it's not just focused on customization you can change a lot about it but when you want to fully overhaul it like I do here to just have like a simple little SVG in the corner that's going to take a bit more work we are working on a custom hook that will make this much much easier so check the Pinn comment with any and all updates to things that have improved since this tutorial was recorded there's anything there but upload thing instead use the Guist in the description and we can work with that let me go copy the code that I'm going to put in that Guist also before the code that you're copying over works we need to make sure we update the util file for upload thing to actually export the hooks as well so if we go into utils upload thing export const use upload thing notice that this is in an object because it's a value that exists within the SDK well within the thing that we're generating so we're going to generate the react helpers which we don't necessarily need all of we actually only want use upload thing for now but again this is the thing that might change check the pin comment so now we want to generate this helper so we'll call gener at react helpers which should automatically fetch from our package we want to pass it the r file router as the type there and now we have the use upload thing hook so as promised here's the Guist you just click raw copy paste and now we have a great starting point I'll quickly TLD drr what this hook does because the future one's going to be very similar I use the upload thing hook to actually get this little object that has the start upload call and any other things we want like in this case permitted files because you want to check if you're permitting more than one file to be uploaded I'm being lazy in just making accept all images ideally you'd be using our metadata for that which is again why I'm excited to have this hook hopefully by the time this is out you can just use a custom hook that we're providing but if not here you have it so that we have this let's actually start working on this component function simple upload button I'm not going to need any props for this we could just do this ourselves return div button upload cool let's go switch the button on the top nav so that we actually can see it when it's working simple upload button import that we are good to go there and now if we go to the homepage we should see this awful button say upload it doesn't do anything so I actually already made a dumb mistake if you're wondering how annoying these things are going to do right this should show you why again we're going to make these things easier with upload thing this actually needs to be an input and since it's an input it can't really have body contents which makes this very annoying but we actually want to get something to put on this input which uh is going to be annoying first we're going to label it though just to make it easier to actually style so we'll call this upload button and then we can have label HTML 4 upload button I'm wrong again it's not label it's ID oops and now this HTML 4 will affect this input we to go to type too so type equals file and now if we go back here we have the good old classic super ugly choose file no file chosen so you need to get rid of all of those Styles and fix this the easiest way to make this go away because again we can't really do much Beyond custom Styles and so just kill it class name equals Sr only thankfully when you click the label it will still trigger the behavior so we don't have to worry about hiding the input itself because we can just use the label as the thing that describes what we want to do but we actually need to do the thing we want to do which we can use our new custom hook for so const I'm just going to make an empty object here for now equals use upload thing input props oh no we're going to type error well if I do that we see image uploader Auto completes we actually want to use the data here so again I'm going to control space to see what options we have input props that sounds like what we're looking for so I'm going to put that over here drop it in like that and now theoretically I click this and I upload something it should work so I just made that Center div video so we're going to try that doesn't seem like anything happened but if I refresh the page we'll see did successfully upload so uh we should probably make the page refresh itself again I could go do that in the top level like we were doing in topnav before but honestly should the topnav give a chit about what the router is I don't think so so I'm going to move this over here where the client stuff should actually be happening import used router again make sure this is coming from next navigation not next router for cell please fix go back here we don't need either of these anymore hell we don't even need this to be a used client anymore how nice is that because the client component is much lower down the tree generally I try to make the client components as deep into the tree for react as possible and give them as small and focused a behavior just makes it easier to maintain and also means you're sending way less JavaScript to your clients when you don't need to so here we have the con router is use router you need to actually use this though and here's where some of the helper functions I built into this become really really handy we also can pass an object which includes things like on client upload complete on upload begin on upload progress Etc we'll be using all of these but for now we're just going to use on client upload complete CU we want to router. refresh we don't even care about the response so I'll delete that and now if we do another test upload we just click that we'll do the random carbon source code I have here whatever that is H oh it did work actually I was just slow oh cuz that image was super highres that's why cool let's uh do a less absurdly highres image how about uh GH CLI and uh I break it again no it was just taking us time cool yeah my internet's kind of fried at the moment but yeah that worked we see this one image isn't quite fitting we'll have to screw up the layout a bit for that could be a challenge for you as the viewer but I'm going to pass on that for now cuz I'm focused on other things speaking of other things I do not love the upload text what I really want is a nice little upload SVG so we're going to do my favorite thing to get little svgs we're going to go to Hero icons we'll search what we want which in this case is upload and here we have a couple options one of the really cool things about this page in this site with hero icons is that I can just click copy jsx go back here make this something so function upload SVG turn paste and now we have this SVG and I can go down here and change the content here be set SVG Tada we have a real icon finally but uh we can do better than that first we need to have some space between those two things so if we go back over to the top nav we can fix that we already have these in a div together so let's just give them a gap and try Gap four initially that looks right we also want to fix the uh the alignment which would be items Center cool now everything is vertically aligned we have a much nicer little upload button there that we can click and do an upload I'm also noticing my cursor isn't changing when I hover over that even though it does on other things obviously that should have a cursor Behavior which is the last thing we need to change so uh we need to class name this and this would be cursor pointer Tada nice and easy and now we have our own custom upload flow wouldn't it be nice to know that something is uploading though definitely something we should fix there's one specific way I like to notify users for stuff like this and it's a toast I don't want to go build a whole toast component though so I'm going to do something I should have done a while ago which is set up Shad Cen so I'm just in the Shaden UI site I got to it by Googling but ui. chad.com what we want is the toast which you might have seen the short of me freaking out over this toast if I click this we get this really fancy nice slow popup of the toast open multiple oh okay I see why you said to do that do you see how good that looks oh oh oh God yeah that's really good I'm sorry I did not expect that to be that good but again that this is the benefit of shadu ey toast cool let's actually get this implemented since we haven't set up any Shad UI stuff we're going to have to configure it but that is totally fine so we should run this command to get started I have a feeling it's not going to work though I'll show you what I mean in a sec pasted and we need to init so we'll just paste the command again but instead of doing add we're going to do in it you know what before we do this I'm going to do one last thing get status I want to add all the things we just changed and the helper that we generated because the addition of Shad UI is going to a bit much for one command so cool we'll do a custom upload button for now and once again well I guess for real this time let's run the init command for Shad youi we'll do default I of these colors SL it's definitely the best one in Tailwind uh yeah we'll use CSS variables for colors let's kill and reload the project so that it doesn't get too mad at us go back to Local Host and everything's going to look different and wrong which is totally fine we can fix that Yep this all looks different and wrong it is nice that it looks like okay with the default light theme without us changing anything but I do want to change things the easiest way to root Force Dark mode which is what I'm going to do here I know I'm the guy who charged for light mode be me all you want we need to make some changes here oh don't tell me they overrode yeah they overrode the stuff that we did for the dialogue so we'll fix that quick dialog modal add that back quick we would have seen that in git but I'm just going to do it here and now everything still should open and behave as we expect but I want to force dark mode as I mentioned and by far the quickest way I have found to do that when using something like Shad UI so go to your root layout and add one quick class of dark and now everything's in dark mode they're using the Slate color I might go swap that to be black but for now I think this looks great isn't that nice good stuff so I'm going to commit that because that was a heavy thing get add- a get commit DM setup Shad UI technically we finished setting up the upload button but I want to make a separate thing for Shad UI fi specifically toasts so we don't forget now that we're all set up you might have noticed the background color changed we could fix that by going into the global CSS and adjusting some of these you just change the background percent here to zero and now be black but honestly I'm liking the blue let's leave it at the 4.9 unlike something like material UI Shad ceni is really focused on giving you full control by putting all of the code in your code base it's not like something UNM install although it does have packages that it'll bring alongside the coal of it is much much different it wants to give you all the pieces to build your own design system in your code base you'll see what I mean when we set up our toast component so previously I had opened up the toast component in here which uh honestly wouldn't blame you if you did the same thing but I don't want to use that I want to use the fancy really nice looking new one which is sonor it's an opinionated toast component so you have have both in here but I don't recommend using this guy sonor is much cooler totally didn't just waste a bunch of time accidentally setting up the toast component twice and getting really component and getting really confused so uh click here click pnpm hop back there paste enter wait a second and now we are good to go and again if we go back to our code base and look at the package Json you'll see it's installing new things like next themes and uh sonor which is the package that does all the stuff that we want to do here but also if we hop into our code you'll see that there is a new components folder this was created by Shad UI and in here we have the sonor TSX nice stuff so let's actually set this up there is one more step in the instructions here which is we need to put the toaster which is where the toast notifications will appear inside of our code base specifically we need to put that somewhere in the root so I usually hop to uh layout TSX go down to the place that we have our modal rout already paste this here instead have to import it and now we should be able to toast pretty easily you'll not that we don't even need a custom hook as long as it is a client component this code works so we hop in here I'll paste that import straight from sonor and now we actually want to use this so let's do such down here in our image upload use upli thing input props hook we can pass an on begin or on upload begin is the name of the function then here we can actually do the toast so let's toast Dot and you have a lot of options here we can just call it directly I believe um yeah we can just call it directly but there's also things like toast. info toast. error that will give you different States depending on what you want your toast to look like but we we just wanted to say uploading so let's do that cool and now if we hop back to the actual project I can upload a new photo click here we'll do my Adobe versus canva [Music] original we see we have our fancy little uploading here how nice does that look but it's done but it's still there we need to dismiss this one this is complete and we also probably want to make sure it stays there when it's not complete so the first thing we should do is making sure it stays there so we can with the customization object there's a duration I'll give it something absurd we'll just do like 100,000 I believe this is in Mill seconds which we can check by hovering that I guess we can't check by hovering that I remember from the docs but if you don't remember you can always go to the Shad UI docs click the API reference wherever they put that link or here the docs which brings you to sonor because again this is just code that they wrote for you built on top of sonor so we can go to the docs for sonor take a look at all the things you can pass toast in here duration is one of the options does that actually say what it does though yes duration is a time in milliseconds so 4,000 be 4 seconds 100,000 be 100 seconds sounds good to me we also want to be able to dismiss it though but we need an identifier to do that so we'll let the auto complete do its thing we'll call the identifier for this toast upload begin the catch would be if we have multiple named upload begin like we did multiple uploads at once this would break that's not that big a deal if we really wanted to we could set up a system to keep track of the currently active toast and make sure the right one is being dismissed here ideally by setting some type of identifier or doing it on a per file basis I don't care though just having one toast for the current uploading State and dismissing it when it's done sounds good to me so let's first dismiss that toast once the upload is done so we now dismissed upload begin toast dot we don't even need to do something special for this we'll do just do upload complete cool and if this is all done right we should automatically dismiss it when it's done and get a new Toast of upload complete upload yet another file we'll do a a new Affinity versus Adobe uploading dot dot dot cool to add a little animation like an SVG there for loading state which we can do in a minute but we need to make sure it works first look at that upload complete and the upload is there isn't it cool how easy that is to set up now like these types of things used to be so obnoxious even now when I was originally like setting up the project and I Ed the wrong toast component it was just so much more work but this is how easy it could be and also how good the results can look in the browser like that looked phenomenal as always it's important to commit so let's commit that add Shad sonor toast component cool I want that loading State though CU of who I am as a person so the thing I like to look up for this loading spinner SVG and here we have a nice GitHub that I've relied on way too much SVG Spinners it's a collection of spinners that you can use for something like this I really like this one like the small end circle with the big gr out part you can grab it as CSS which is probably the easiest thing go to the code here we are and here's the SVG that has everything we need for this so there's a lot of things we could do to make this SVG generally speaking you should be putting your svgs as like assets somewhere but if you want them to always be in your code not have to be loaded externally it's not a bad idea to just throw them in as code so let's actually use loading spinner const I guess I used function everywhere else so we'll use function here loading spinner SVG oh shush you sorry about that my cat is shouting as always one important piece here is it has some Styles in it as well in fact the styles are most of it so we should take care of those buddy I'm working gu we're getting a quick cat break M says hi and he hopes you guys can finish the tutorial soon more importantly that I can finish this tutorial soon this taken me forever this is my life it's a lot of claws anyways now that I'm covered in cat hair and all sniffly from it we need to take care of all of this style and also looks like I didn't copy over properly so I'm going to just delete it all just going to click the copy raw instead I'm going to go drop this in the CSS file for now some of it's going to have to go there anyways so let's go to our Global CSS Let's uh paste this at the bottom because we just want to grab the style tag from in here and everything attached to it so that's this guy yoink obviously this is a CSS file so we no longer need the style HTML tag wrapping so get rid of all those and then take what's left save in order to let that auto format look at that now we have the spinner with its weird name as well as the key framing for it go back here paste what's left I told that class isn't valid because we were in jsx change that to class name and now we have a loading spinner SVG so we need to use this thankfully the toast here doesn't only take strings it can actually take jsx so if we do a div put the loading spinner in it that's already here and then uploading it's not going to be pretty but it's going to work it also be nice to just see this immediately so I'm going to be lazy and throw a use effect here that does that this will on every render root Force this to exist so that I have it there for testing purposes oh boy was that mad about that is my cat again buddy I thought we had come to terms to this I have to finish filming can I finish filming please this is my life it's the one I've chosen to live and I'm afraid I have to kick him out anyways that is done I'm not actually seeing it when I go to the page though toast it looks like it doesn't want that there until some user action has been completed of some form we could do things to fix that I could add a button or something for that honestly I'm going to do something real dirty just to show you guys the types of tricks I do to to debug like this I just want this toast to work and look good and I want to be able to test it easily and since we can't Mount the component directly we need some way to make it appear fast I'm going to do something real dumb I think you guys will understand why function make upload toast just so we have the same thing for both return toast cool and now for the stupid thing window. make toast equals make upload toast cool that's not something the browser wants you to do or that your ID will want you to do but that's fine now we have window. make toast that we can call and see the toast and here we can see that the toast is absolutely screwed so let's fix that class name equals Flex Gap two feels good to be the gap between those two elements and uh text white we hopefully fix the loading Spinner's Color Run our Command again that did not fix the color but everything else appears to be good took me a second to remember I just need to put a fill color on the SVG and now that that's done we go back to our console to see it actually working look at that there's a little bit we can do to fix the alignment because that doesn't look quite right so let's do that first we probably want to wrap this I'm going to put it in a span class name equals text XEL a little bit bigger on our little command again that's too big text large that's a good bit better and then we need to items center for the alignment look at that perfectly vertically aligned reasonably sized text nice little loading spinner doesn't that look great I think that looks great we could call the function directly but honestly we only needed it for this testing we're not reusing it so I'm just going to yoink this again and drop it back inside here again and now we have this all handled think it's fair to say that's a job well done let's save this and start working on the next task get status get add Dash p coder interv viw ah actually we just caught something we don't need this use effect anymore CU I was just using that during debugging so let's redo the staging that it yeah commitm add styling and loading spinner for uploading State I get push that as well and we're done like legit at least for the UI component for this project obviously there are lots of more things we could do we could quickly change the layout of this page so things are more consistent lots of fun challenges for you as the creator of this I still love this though so we can open a modal when we do it you get this nice full View and if you refresh or just link straight to it you get this dedicated view that looks significantly better now that we fixed the top nav stuff yeah this looks great I actually built something pretty cool and useful here but there is still a bit more I want to do in order to get this truly production let's go back to our read me and see what's left so I just made some changes to the to-do because I realized that I honestly want to do analytics first because the delete button will make much more sense with it make sure you stick around for the delete button stuff though because it is really really cool some of the coolest stuff that exists in this new model we did also just finish all of the cleaning up of the upload button though so we can finally cross that one off so let's hop over to analytics if you're not already familiar with post hog it is a really cool product go to post hog.com the drr it's open source product analytics not just like which Pages people went to which buttons they pressed which user groups are doing which things it's really useful for getting more general info about your user base and being able to identify patterns conversions all the things you care about when building a real product there are other options you should definitely consider like if you're not really having signed in users you just want to see what pages people go to plausible is phenomenal and also open source but as they say here they're much more a Google analytics alternative that tells you how many people went to your pages and which Pages they went to not what actual behaviors the users engaged in which is what you want to use post talk for as such I highly recommend post hog I like it so much that I've used it for all the things I've built for the last 2 years and I actually reached out to them and I'm the first Creator they've ever sponsored because I didn't want to work with any of the other companies that were interested because I don't use those post hog is what I use and ship they're what I trust they're why combinator rum they're open source they're a great option we need to actually use it so let's sign in Click the dashboard or sign in button here we need to sign in I'm just going to use GitHub because of course and here we are I'm getting all these errors because I have a weirdly configured new um organization when you make your own org you won't have these problems we want to create a project though so let's do that uh T3 Gallery tutorial create project so now we need to actually set it up you can just go to product analytics get started they have their little guide here which is honestly fine to get started but we want to use next so let's click this instead we install the post hog JS package actually have a pnpm command for us there we'll paste that we'll grab the public post hog key and host as well throw those in your environment variables ignore the ones I already have here honestly ignore all of them because I don't want you copying my stuff but uh yeah the next public post hog host is the actual URL and the post hog key is the actual like key that identifies which post hog project you'll notice it's public that's intentional this key can only be used for writing theoretically somebody could use that to spam your service and clog up your analytics but usually when they do that it's pretty easy to identify that they're not real especially if you use something like clerk to identify which users or which it means you get real data so now that we have that done we can hop back over here and copy the recommendations for initializing with an M router so we copy this this is the use client code it's ajs file sadly but we can fix that up over and it's going to make a separate folder in our route well our app route that's named uncore analytics just to handle this all and we'll put in here provider. TSX paste save mad about children children react react node cool it's also mad about the post do key we just put an exclamation point there for now we could update the EnV JS for T3 environment but it's not too big a deal we'll get to that later now we have that marked correctly need to actually use it they show how to use it here CS post hog provider go back over go to the layout TSX and in here important piece you want this right below your clerk provider but above everything else because you want your post hog provider to have access to the stuff that clerk provides so that you can identify the users so we paste that in there now that's all done we want we can send a manual event too but I'm more interested in just making sure it works let's save the changes let's run our Dev environment again ex I to close it when I was testing some things off recording I have to sign back in cuz I was testing things and now we're back theoretically we can just click around go to a few different places and now if we go back over to post hog see the installation complete it knows that because it got some events from us so it knows we had to have set this up right we have the option to autoc capture fronted interactions which is really nice it basically just means anytime you click a button and things like that they'll collect some info about it they also can capture location information which is also pretty handy some people will be sensitive about that so makes sense to uncheck if you're one of those but not a big deal and then you see once again the free deal is insane a million events a month for free and the price for additional events is hilariously cheap uh yeah it's a very good deal let's scroll down and click skip for now this is the page you'd invite any teammates it's actually free to do such unlike most Services now we're done it says no events sometimes it can be slow to actually show the events like 10 to 15 minutes but it's much faster to identify people so if we go to the people you'll see here's a people and this people has information they're in San Francisco they're United States that all looks pretty good to me useful to have this info now that we have this info it' be nice if we could actually identify who the user is and get a little bit more so I'll show you how to do that really quick with clerk remember how I said it's important to make sure the post hog provider is underneath this is why I'm going to do one additional layer in here cuz it's the easiest place to put it function post hog off wrapper which also takes children we don't want any of what it put here but it's a good enough starting point what we want is Con off equals use off which should come from clerk nextjs nice and then we want to use effect I know use effect so scary I'll explain why we're using it in just a second what we want to do now is use the O info to effectively keep track of the user info so we can identify them cool so if post hog. identify and here's where we can identify them via the user ID there a lot of additional stuff we can use here but we'll use off user ID to be the distinct ID so instead of checking if off that is signed in we check if off. user is it off. user ID yeah it's off. user ID like that's a fine starting point but I would like to have a bit more data so we going to return children just to get that to go away but we do want to identify more here so we could get more info const user info equals use user yeah I know this seems like a pedantic difference use Au and use user but user info gives you way more information what we actually want here so if userinfo do user and we want to use the stuff that we got here so we'll drop this in o. userinfo do assuming ID is an option cool it is and then here we can put whatever additional info we would like you hover over identify you can see all the things that they allow you to pass it can be email and then a bunch of additional info under the next part we'll just use email for now email userinfo do user. email address cool and we'll throw an else case here of post hog dot yeah recess probably the easiest thing to get rid of current state we can look here yeah clear Super Properties and generates a new random distinct ID for this instance useful for clearing data when a user logs out look at that so smart honestly what we should do for this is else if if not off is signed in then we want to reset so here we put off and user info and now we will identify the user when they are signed in and we will reset post hog when they sign out should probably actually use this cuz I'm not using it here so let's wrap this guy around children TDA and now if I refresh and navigate around a bit and we go back over here should actually be able to identify me oh it put all of that stuff in there because of how that works that's actually annoying I forgot about that so we need to use not email addresses but email address is z do email address cool I'm going put a bit more in here I think this name I think it's full name or something like that yeah full name once again navigate around a bit force it to get that new data over the new identification refresh this and look at that it fixed the email we have the picture cuz it's using my gravitar we have all of that isn't that nice really useful when you're identifying things and keeping track of the data looks like the page events are still taking a little bit to parse I told you can take take up to 10 minutes but uh those will all be there you can trust that when it gets there you'll have all this data to analyze in the future when you want to see what users are actually doing on your services but since we've seen this worked we know the rest is going to although we want to make sure this will work no matter what the user's setup is like specifically we want to make sure this works even if they have an ad blocker installed which is not as simple as it sounds thankfully there have been some really nice Solutions created to this over time if we search and go to the Post hog docs they have a little framework section if you go to framework guides next and scroll to near the bottom there's a couple things in here that are important one is they have a guide for setting up the node side which might not seem useful for next but I'll show you why it is in a little bit so we'll do we'll be coming back here for that the reason we're here right now so we want to use the nextjs rewrites in order to make sure we always get this info even if the user has an ad block installed the key to that is we need to use this async rewrites pre-built config hop over to our next config and add this into our core config so in here we're going to have rewrites and we're going to add this new one SL inest just paste the exact options they have there and we want to go back into where we have our post hog and knit configured and they'll show you the code that we need to change we need to pass a custom API host and UI host so uh we're already using the API host from the environment variable we will no longer be using that instead we're going to be using these they should have put a comma here they forgot to it happens we also don't need to put a host because the host is going to be the same URL as our app so we can just do SL inest there and that should just work now if we go over to here and we open up the network tab what we should see is all of these weird e events this EIP equals this guy this is going through Local Host inest yada yada we can see the response to status one we can see the payload is a bunch of jumbled stuff that is the weird format that gets sent to post hog so that the data can't be analyzed on its way there we have everything we need that all works and if we go back over here we can see my user still exists nothing too weird has happen happened still no events showing up just yet but as I mentioned those will eventually appear as per always I think now is a good time to commit get add a get commitm basic post hog analytics but we want to actually track things that are being done the first thing we want to do is track when a user starts doing an upload which is pretty easy to do if we go back over to the upload button I'll show you just how easy that is simple upload button we can go back to the guide and I'll show you how they recommend it and we can work on it accordingly I'm just going to go into the react examples now so I can show you how to consume it because in the end we are just using this the react way here we are post hog. capture just need to use the post hog helper you get that by using the use post hog hook it is really simple I didn't really need docs for that I just wanted to show you how I would find that type of thing so in here similar to how we added the integration for that uh toast we want to add one for use post hog so we import the post hog react code scroll down to on upload begin so you can paste post hog. capture upload begin now whenever an upload is begun we'll get an event for that which theoretically we should be able to see by hopping over here I'm going to open up the network tab again so we can actually see these events as they go through Network cool I'm going to clear so we don't have too much junk in here that we don't want going to click upload we're going to upload uh the Affinity sorry again and we see here events going through and theoretically some or most of these should have been the correct event and while we'll see we can't actually see the events yet if we go over to data management we'll see there is an upload begin event that was seen a few seconds ago again the actual event will take a bit to appear here as they're processing all of the data but just the fact that it appeared there means they did at some point see it that's how you know it's working A+ s it cool that's how e to set these things up sure sure sure get commit upload event capture nice I'm going to call that one a mission success and now for one of my favorite Parts the delete button the reason I'm excited about this is we get to finally show off server actions a bit so let's figure out how we want to architect this what I have in mind is we're going to use the not the modal the where do we put the full page image this guy cool so in here I want to have a delete button probably right underneath the created on bit under here maybe we'll just do button delete if we go back and load that somewhere we'll see this delete button obviously doesn't do anything we just threw that there so how do we actually make this delete button at the very least look good well the look good part is pretty easy with shad UI again we'll go to components button this time and see if we scroll there's a couple different button Styles typically there's a destructive style which means that we have what we need if we install this so let's copy the pnpm command go back here paste it and now we should be able to use the capital B button from components UI button nice switch this over as well and hopefully now that should look quite a bit better look at that delete and it also has props which is what we want specifically we want type submit we'll want that in a second I'll explain why what we want is that destructive button I could guess how you get to it we could also just go here and look so let's look at the code for the destructive variant equals destructive as as PR usual with the naming with shad UI a variant destructive delete if we go back look at that nice red delete button how it always should have been but obviously clicking this still isn't going to do anything we wanted to do two things well I guess three things we want it to delete the image we want it to send you back to the homepage we want to make sure when you're back on the homepage that the content is gone what if I told you we could do all of that with one function on the server this is one of the coolest parts of server components so first we need to make this a form because if we don't make it a form it's not going to work without JavaScript the theoretically we could make this a client component and do a Fetch and things like that on the other side but if we just make it a form like this it's much easier to avoid having to do that because now we can use actions form action is where server components become magical we have to mark this as use server the reason we're putting use server here is so that we can be sure that this functionality gets exposed as a post endpoint on whatever Pages this component is mounted on it's a bit of a mind when you first discover the power of this but when I show you here what it does I think you'll understand theoretically we could break this out somewhere else but if we put it in line like this we get a few magic superpowers I know I just said that we should put everything in the server file and you know what I'm going to stick with that we'll hop in queries and we'll do this right con users off we can still make sure the user authenticated to do the thing that they're doing here because that's important otherwise they' be able to delete things they shouldn't be able to so we don't have the ability to do db. query for this sadly db. delete and this needs to take in a table do where yeah it's images from the database schema we have where model and we need EQ for the help property EQ any that can't be right I need to import EQ that sounds likely actually import that from there and when you import the model with the database schema like this you can just use that as the reference so db. delete images where equals image id id you could also wrap this with an and which we can import from um Jal as well so we'll do the and ID EQ images user. user ID now this guarantees that we can only delete images that belong to this user since we have that check there we actually can delete this whole part too because we're only going to delete the image if the ID matches and the user matches too we were returning the image but there's something really to return here so instead we're going to do something really convenient we're going to revalidate the path of Slash because we're going to send you back home we can also redirect you here too to slash now if I use this function in here we can just delete the image for the current image id id is number cool import that it's mad because we're not awaiting it so we'll do async for this await delete image for that and now this should all just work we'll try it on this first Affinity Adobe sarv 2.1 loads deletes and it's gone isn't that nuts I can even turn off client side JavaScript and that still works so we'll get rid of this one it's gone we'll get rid of this one it's gone the fact that it navigates that quickly without having to run JavaScript is primarily because all the work's being done on the server since we're handling the valid ation of the path and we're directing you there it just behaves how it's supposed to in fact I don't even think we need to revalidate path because the path is changing it should be smart enough to not need that so let see if we can get rid of this v2.1 look at that we can can I get rid of the last one too A+ normally you'd want to fake this with optimistic updates and client side validation type stuff you don't even need that anymore you just write the redirect in This Server function and you're good to go it's actually that easy I love this for a ton of reasons but it's it's honestly just mind-blowing that I can write code like this that's on the server and this codee's also on the server and the U server call is effectively letting me bind this function to this form on the client with no JavaScript running on the client no JS is shipping here at all in order to get this ID it's actually embedding it in a mini hidden form field so if I inspect this button here we can look through and see that there's a form action there a new error this is what it does if it's uh being called manually just like giving you a fallback Behavior there the important piece within this form is that data gets embedded in order to make sure it behaves the way it's supposed to I actually thought there would be more hidden here I think they're moving it to other elements but effectively what this form is doing is just embedding the data that we need here it is I just refreshed and now I see it so see all these input type hidden things in here these are how it's actually identifying things like the image that we put in there because it doesn't have that info CU it's just posting to this URL it gets that info by parsing and decompiling the values that are snuck into here it's a tiny bit of a hack but it means that you can have one post end point and will ident which function it should call accordingly and even from this fancy SL image page if I click delete it brings me back home with that content removed it's so cool it's insane that it's this simple to do this stuff I did promise that we would analyze and like track this first I want to commit it though so okay I actually do the get add- a Comm in- M uh delete button push that up but I want to actually add analytics for this as I mentioned before this is something we're going to want to do with the post hog instructions so if we go back to the nextjs default instructions here next command f for node tells us to install the post hog node Library pnpm paste for the add post hog node get that in scroll to the app router examples here is where we create a post hog client this will run on every server invocation if we import it on the endpoint that it's being used on I guess this should be on server so we'll do server analytics. TS paste this it's mad about the key again that's fine we'll use the public host for this cuz this running on our server so it can't be ad blocked has some aggressive flush rules it's going to run in serverless totally fine but where's there's a couple more things we can do here so I'm going to rename this a serverside analytics because it's a little more accurate for what it is now we have everything we need to create and use the client generating these is free so this is fine honestly what I normally do is export const server what do I want to call this analytics server client equals that I spelled export wrong typing is hard cool going to get rid of the default there I'm even going to get rid of that export default Tada and now we have this and now if we want to use it like we do in here for delete image I'll show you how we do just that paste Auto Import do capture delete image and here's where we can put all our metadata I think there's a specific metadata field uh we can just check in an example in their code properties okay this is what we want to put most of our stuff under there's also a distinct ID which is really good for putting the user ID so we'll do that first distinct ID oh they don't even have the string in the front like the other things do that makes a lot of sense so distinct ID is the user. user ID makes sense that way they can still still log and keep track of stuff event have a name most people do events with like underscores and like that I think that's dumb I like to actually make a string and funny enough they do the same thing user signed up I actually had an analytics provider in the past send me an email shaming me for having envirment well event names that looked like this that were actually descriptive of what was happening it's like oh your event should be named in this specific weird fancy bem like syntax and I think that's stupid eventually be human readable because humans will be reading them I just do that we want some properties too properties are really useful for a bunch of things if we wanted to like analyze how many images are being deleted if this was multiple if we wanted to like keep track of what types of images were being deleted we collected all that type of data stuff like that we could put that all here for now we'll keep it simple just the image ID and now we should get an event when a image is deleted again trivial the test load our page pick some image we'll do the center div v3.5 we'll click delete now it is gone and if we go back over here should be able to go to uh data management and see a new delete image event Tada it's that easy and now we can throw that in anything that we want to we also if we wanted could wrap the analytic server client and include distinct ID automatically so the user just passes everything else it's a little Annoying I actually have a video where I show how I make a bunch of things type safe I uh will leave the link in the description if you're curious about that safe libraries Theo here it is how I deal with unsafe packages this video is me talking all about exactly what I'm describing here so if you want to see what it looks like to set this all up there's the video it's one that nobody clicked and I recommend it how I deal with unsafe packages dope let's Commit This and get done with our last step isn't it cool how much we've done and so little time yell feel like you're better understanding how to set up a full production ready project cuz I'm certainly feeling like you are get commit uh analytics on delete button Tada I still love how little this affected the client and again that's kind of the magic since server actions can just be embedded inside like in line functions it keeps you from having to like do the whole thing where you define an endpoint and then access it and then behaviors change and all of that instead you're just importing a function so if we go back to our component there the full page image you'll see in here like just you server await delete images we could have even put the content of this function in there it would be totally fine acceptable to do such because this function will never touch the client it only runs on the server and we could put multiple of these here we could put one for each number for each ID in here and it all gets defined the same way any component would it just so happens this only runs on the server actual magic it's I I still get my mind blown with how simple and cool it is to do like this in the new model one more thing I thought of that is probably worth calling out in order to make sure you don't accidentally use the wrong thing here I recommend putting this as import server only just to make sure it doesn't end up somewhere it doesn't belong get add get commit ensure analytics server only runs on server well cool now that's done we can do our last step which is rate limiting rate limiting is super easy to set up thanks to the first ever sponsor I had on the channel up stash they have been phenomenal to work with and if you didn't already know this uh Josh tried code who it's another phenomenal web Dey YouTuber you should check out actually somewhat recently joined to work full-time over at up stash yeah I got a job at up stash just a month ago super hype form I think it's a great fit and it's a great product so I'll just click the sign in with GitHub button here now we're signed in you see I have a ton of stuff here there because I've been using up stash for testing and rate limiting random services on my personal GitHub forever so we're going to go under redus yes they're still allowed to use redis cuz they technically not reddis under the hood we're going to do a regional setup I know isn't it better to go Global sure but when we go Regional it's less likely to hit edge cases that would result in the rate limit not being as strict we can name it so we'll name this Gallery rate limit create now we're going to Google search for up stash rate limiter up stash rate limit they have their install command we'll pnpm add up stash rate limit and here is how easy it is to use it's actually kind of magical I will y this we will go back to the server F or folder I'll put in here rate limit. TS paste this export this we don't have the UPS Rus package installed so let's quickly install that you can use the UPS Rus package with other services because it really is just a Rus client it's just the best redus client going to delete all these comments because we don't need them and now this Redd St from environment command should just work what's I'm out about turns out this is just typescript being weird once I restarted my editor we're fine I yeah you get used to it over the years typescript even when it says it's working doesn't always work I actually want it to be in here cool so we want to actually be able to connect to this if we click node uh hopefully it will give us EnV cool just go here read only that's fine copy H back over here go into our EnV paste our final environment variables of the tutorial we'll have to move these all over to be on the uh versel stuff but for now this is totally fine and if we did everything right this is now functioning rate limiter we have the limiter we have all of this let's actually use it though if we go back to the sample code here we can see how they recommend using it which is calling rate limit. limit with an identifier in our case the identifier is going to be the user ID so if we go back to our upload component well not even the component we want to do this on the back end that's the whole point so we go to API upload thing core and now inside of our middleware once we've off the user we can make sure they're actually allowed to do this thing pretty easy to do we import our rate limiter and we have to give it the identifier which is user. user ID and now if not success we can throw different error look at that if no success throw new upload thing error rate Limited now if the user shouldn't be doing this they very limited going to delete all these comments I actually think it'll make this file a lot cleaner now would you look at that we get the user if there's no user ID we throw that they're unauthorized we get the success status from the rate limit and if they're not successful with that then we throw a new uplo thing error right now the rate limit's pretty aggressive it's a you do up to 10 over 10 seconds let's do 2 over 100 seconds for now just so that we can confirm upload Infinity uploading upload complete cool let's do it once more we don't actually have this showing anything in the UI but if I go to the network tab oh it let us do a second one but if we do one more now oh look at that we got an error from the endpoint 500 internal server error ideally we' have things set up so you get a better error on the client we do actually have the ability to pass the error to the client already so if we go back to our upload button we have here in on upload error we can capture the error via post hog which is really useful didn't even think to do that cool that it autoc completed and did that for us toast. error upload failed we'd also want to dismiss the upload begin here and I did that all right hopefully within under 100 seconds that one will probably upload because it's been long enough that the rate limit is going to forgive us but if I do another right after upload failed look at that how easy is it to get that working I love that obviously you probably don't want the rate limiter to be that aggressive I'll bump this to I don't know 10 every 100 seconds there's a bunch more stuff you can do with this like the request has a ton of useful info here you actually get things like the IP address through this you can check the next docks to see what specifically your past here or honestly just console log it and take a look at the stuff that you get because of that you can actually use things like the versel ID inside of the rate limit along side a user ID but honestly if you have a user ID you're probably going adjust that that's more useful if you have like an anonymous service that you want to prevent from dsing but this is how easy to set that up so let's Commit This get commit DM rate limit implemented I'm not going to push yet there's a reason let's go back here let's go into our environment variables and since we're using the same environment variables for Devon prod which obviously we shouldn't but we are going to copy up to upload thing CU that's where I think we stopped updating things on versel I could be wrong we'll figure out in a moment hop over to T3 Gallery settings environment variables scroll down it looks like I did get upload thing as the last one so we actually only need to select from Sentry down so we yink all that paste all that here we hit save and now it's done so if I go back here and push theoretically everything now works and we have a fully functioning gallery application let's go watch this build because it's the most important one we've done so far fingers crossed y'all now it is done T3 Gallery all of. for cell. app we have all our images all loading using next image we can go to an image it will load the page we can delete the image it'll send us right back and the image is gone and most importantly if we hop over to our analytics we should now see all of the events for all of the cool new things that we're doing in production we have all these production user identifiers which uh are probably just me in all those different environments I was playing with specifically also the page scraping that versell does in order to show you that like nice little page bit we also should have all of the events that we're getting again events take a little bit to propagate but uh all new ones should come through here just fine because we have done it the server is up and we want to make sure that those are going through it's pretty easy to do that as well let hop over to the network tab make sure that our ad block isn't killing them now look at that we get all of these events the E events for the events and the delete is going to process on the server side anyway so there's no way that can really be stopped when I was wrapping up I realized I forgot one of my favorite parts and I wanted to make sure I included this this because right now any user can upload which might sound great for a traditional service but I want to make sure I can leave this public and not do such in a way where everyone can access it and upload all sorts of stuff so I'm going to show you the best way to lock down uploads in this app you could do this in a lot of different ways if we go to the code and we look here at the image uploader middleware which is an API upload than core you'll see that we could just lock them here we can make sure the user ID is the right ID or we could check the email match as a certain email that we have in a hard-coded list but I want this to be dynamic I want this to be a thing that we can easily go change the best place to handle this is going to be your off ideally and we can do that by embedding different data in your user's off information best place to do that's obviously going to be clerk so I'm going to go to clerk in my dashboard we're going to go to users and we're going to pick one of my two accounts I have this one where I signed in with Gmail and this one where I signed in with GitHub so we'll pick this one for now this is the one where I sign in with Gmail you can see cuz social accounts Google true so we're going to scroll down to where it says metadata this is one of those cool magic things that clerk does really well for us this lets us embed data that is public or private or unsafe well I don't quite know what they want us to use unsafe for I do know what they want public and private to be used for public would be metadata that you're okay with other people seeing maybe it's like your chat color if this is a chat app you could throw that in public and then everyone sees the hex code that your chat color is supposed to be this still can only be updated by a server that you're running but everyone can see it private metadata is stuff that only your server can see so this is accessible on the server but no one else can see it probably a good place for something like this so we'll do just that we're going to edit the private metadata we're going to add a new field can- upload and we're going to set this to to the Boolean true not true in quotes just true the value because that's valid in Json which is what this is so now we've saved this and this is saved on my personal Gmail account so now we can go back to our code and we're going to add a little bit here we have user but this user is just coming from the JWT token that exists via clerk so there's no blocking no waiting no anything that has to be done here but that also means they embed a very much smaller subset of the data so we don't actually have any of the data around what metadata this user has at this point so let's get that data const full user data equals await clerk client. users I have to import clerk Client First do users. getet user and here's where we get the users info via their user ID and now we want to make sure that this user has the right permissions so if full user data question mark docs technically we don't know this data exists but we do private metadata question mark Dot and then brackets because can upload isn't valid JS syntax we have to wrap it like that just so that JS knows hey we're getting this key from this so if this is not equal to true not the string true but the Boolean true we don't need to bracket it it's the next line throw new error upload thing error unauthorized I'm going to make this error different uh user does not have upload permissions so now if the user doesn't have this set to true they won't be able to upload I am currently signed in via my gmail which you can tell because I don't have any pictures here my other account which I did through GitHub has a lot of pictures already uploaded we're going to upload this random picture cool it says uploading that's a good sign and if all goes well this should upload and refresh the page how we would expect yep and there we are there's a new content and if I switch my other account which I'll do quick be right back now I'm back in with GitHub which you can tell because when I click here it has different information and all of the content here is what I uploaded previously if I try to upload now I get an error and if we go to our console we can see what the error was scroll up a little here and you see an error occurred in your middleware function user does not have upload permissions tada is that easy to add off and now at any point you can go to your dashboard and quickly control who does and doesn't have upload permissions by adding this field you can even build your own UI and your own endpoints to make this easier to set up automatically but I did this specifically so I could make sure users can't upload without my explicit permission to do such because if they could that would be dangerous and not good for the service my goal is to make sure that you're deploying something that can actually go out in production safely and if you don't have some way to make sure only certain users are allowed to upload your service is unsafe combine this with the rate limit and now you have a pretty solid guarantee that users aren't going to spam your end points and upload things they're not supposed to cool and of course we have to commit and push this which makes this the final push of the tutorial we've done it y'all one last thing before we wrap up we have finished all of this stuff but I want to pose some challenges because there's obviously a lot more to do a challenge to the viewer here are some things you can do to keep working on this project to prove you've learned these Concepts and post yourself a little bit one of the easiest ones is fix the page layout for images of different resolutions that we didn't really do much for the aspect ratio is different doesn't fit just right there's a lot you can do there it's really powerful really cool and fun one additional fun challenge is selecting images on the gallery page this one's fun CU you're going to need to do some State Management I'd highly recommend using a library like zustand that way you can set it up that when you click specific images they get check marked and you have those all stored in state and then you can do actions that affect multiple at the same time that one's going to be more complex but it's a really good learning experience speaking of which infinite scroll would be a really good thing to add as well so instead of having all of the content on just one page ideally it just shows what's visible and as you scroll it goes and fetches more data in order to get everything you need to show the rest of the content of the page another fun thing along those lines is the idea of like folders or albums which you would have to do by creating a new thing in your database so right now our data model is hilariously simple if we hop over to our DB schema we just have images but if you were to make something like albums and Link images albums have a default album that is where things appear on the homepage the ability to share an album with other people as well you can do some really cool stuff there there's plenty of other things you can do to push the limits of this project but I think this is a phenomenal starting point and I'm honestly excited to see what you guys do with it so yeah have at it I put a lot of time into this one I've been recording on and off for about 6 days I've been planning it for over a month and I hope it shows I certainly hope you were able to learn a lot from this one last massive shout out to all of the sponsors of this video they're the only reason it was possible the only reason I could justify investing this much time into a single project shout out to faze my editor for taking the time to stitch this hours of content together and thank you to yourself for watching this getting through the whole tutorial and making a full stack application using the modern T3 stack I don't know what else to say other than have at it this is my life
Info
Channel: Theo - t3․gg
Views: 305,448
Rating: undefined out of 5
Keywords: web development, full stack, typescript, javascript, react, programming, programmer, theo, t3 stack, t3, t3.gg, t3dotgg
Id: d5x0JCZbAJs
Channel Id: undefined
Length: 183min 11sec (10991 seconds)
Published: Sun Apr 14 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.