📘 FREE TanStack Start ebook (full mental model, 60 pages):
→ https://jsm.dev/tanstack-ebook
🟦 TanStack Start Pro course → https://jsm.dev/tanstack-course
Day Zero access is live for 7 days.
Lowest price this course will ever be.
Price goes up when the course ships complete.
━━━━━━━━━━━━━━━━━━━━
In this video you'll learn what TanStack Start actually is,
why it fixes hydration errors and "use client" directive hell,
and how to think about routing, data fetching, server functions,
API routes, and SEO from first principles.
Then you'll put it all to work building Skild, a full-stack agent
skills marketplace, from empty folder to live deployment.
━━━━━━━━━━━━━━━━━━━━
Clerk: https://jsm.dev/tanstack-clerk
PostHog: https://jsm.dev/tanstack-posthog
CodeRabbit: https://jsm.dev/tanstack-coderabbit
Explore my Pro Content:
⭐ Join JS Mastery Pro: https://jsm.dev/tanstack-jsm
💎 Become Top 1% Next.js Developer: https://jsm.dev/tanstack-nextjs
👨🔬 Master Next.js Testing: https://jsm.dev/tanstack-testing
📗 GSAP Animations Course: https://jsm.dev/tanstack-gsap
📕 Three.js 3D Course: https://jsm.dev/tanstack-threejs
📙 JavaScript Course: https://jsm.dev/tanstack-javascript
🚀 Launch Your SaaS: https://jsm.dev/tanstack-saas
📁 FREE Video Kit (Code, Assets, Figma, and more): https://jsm.dev/tanstack-kit
More courses launching soon! Join the waitlists to get notified! 🔥
👉 Backend Course Waitlist: https://jsm.dev/tanstack-backend
👉 Agentic Development Course Waitlist: https://jsm.dev/tanstack-agentic
👉 React Native Course Waitlist: https://jsm.dev/tanstack-native
👉 React Course Waitlist: https://jsm.dev/tanstack-react
👉 Tailwind Course Waitlist: https://jsm.dev/tanstack-tailwind
➤ Links not working?
If you're in Nigeria, you might have to use a VPN to access the links.
➤ If something mentioned in the video isn’t listed here or a link is broken:
Leave a comment, or contact support@jsmastery.pro
Rate us on TrustPilot: https://jsm.dev/trustpilot
https://discord.com/invite/n6EdbFJ https://twitter.com/jsmasterypro https://instagram.com/javascriptmastery https://linkedin.com/company/javascriptmastery
Business Inquiries: contact@jsmastery.pro
Timestamps:
00:00:00 — Introduction
—
00:04:17 — TanStack
00:08:09 — The Request Lifecycle
00:11:35 — Installation
00:15:13 — Project Structure
00:23:17 — Components & Styles
00:37:12 — Routing
00:48:06 — Data Fetching
00:59:38 — Server Functions
01:11:49 — API Routes
01:18:44 — Metadata & SEO
01:21:58 — Course Outro
—
01:23:35 — Installation
01:30:18 — Layout
01:41:14 — Home Page
02:11:41 — Authentication
02:22:20 — Analytics
02:32:55 — Firebase
02:36:22 — Database Setup
02:51:34 — Data Fetching
03:11:41 — Going further
Оглавление (21 сегментов)
Introduction
Do you really need to learn yet another JavaScript framework to get a job? I mean, really, the JavaScript community never misses a chance to produce yet another framework to solve problems created by the last one. But this one is actually different. Trust me, if you ever faced hydration errors or if you're sick of use client and use server directives across your codebase or if you hate the vendor lockin but still want that full stack server capabilities without the complexity, well then this framework is for you because it does offer all of that. So today we're going allin on tanstack start but not with a normal tutorial. First, you'll get a no BS crash course on the architecture, routing, data fetching, server functions, and SEO. And then you'll take everything you've learned and build something real. Your build skilled, a full stack marketplace where developers browse, submit, save, and share agent skills. Think of it like a curated list for reusable AI agent configs, prompts, or tool setups. Whether you use that kind of stuff personally or not, that's up to you, but it's good to know how to build it for others. So, here's exactly what you'll build. An SEO optimized server rendered homepage with featured skills and categories. An explore page with streaming search results that load progressively as data arrives. Skill detail pages with full SSR usage stats and one click to copy or fork. A submission page using server function mutations with validation. Optimistic bookmarking using Tanstack query. So saves feel instant. The stack is tanstack start with tanstack router query and table for the full ecosystem. React 19 react compiler and typescript for the foundation. Tailwind CSS and shatn for the UI. Clerk for authentication and user management. middleware to protect secure routes and validate server requests. Firebase to handle live database reads, writes, and community upvotes. Post hog to track exactly what your users are clicking and watching. I use it to monitor analytics for jsmastery. com and all of my other apps. And I'll also teach you how to use another tool that I don't start any of my new projects without anymore. And that tool is Code Rabbit, which we'll use to get our code reviewed and up to the professional senior level standard. And I'll also teach you a little hidden use case of Code Rabbit, which is to use it to improve your own skills and get to the stage of writing senior level code yourself. I'll leave the links to all of these tools down in the description. I recommend you to pause this video right now and create your accounts so that later on once we dive into the project, we can just dive straight into the coding. But yeah, by the end of this, you won't just know the theory or yet another JavaScript framework. You'll have a working deployed application live on the internet and the exact skills you need to build your own. Oh, and you'll actually understand when to server render versus client render and why that decision matters architecturally, not just SSR is good for SEO. That's basic. I'll go deep in real reasoning and trade-offs. Oh, and one more thing. I put together a free reference guide on Tanstack. It breaks down the mental model behind everything we're about to cover. So, if you're someone who likes to understand how a framework thinks before you write your first line of code, check it out. It's free in the description. And if you want to go super deep, the procourse has everything from this video plus the Tanstack internals. I'm talking streaming, SSR, route masking, middleware, as well as extra features on top of what we'll build in this video and all the other patterns we're skipping here. Links below. And it's 40% off for the next 72 hours only. So, grab your coffee or tea, whatever works for you, and let's dive right into the crash course.
TanStack
You're still here and that's good. But I know how you feel. You're thinking, "Man, Adrien, do I really need to learn yet another React framework and I completely get it. You already have Vit for standard apps and Nex. js for full stack stuff. So investing time into a whole new ecosystem feels like a lot. But it isn't like that. More than 50% or heck even 70% of foundational concepts you have about web development using React or Nex. js carry over to any framework out there. Not just Antax Start, but any framework you'll ever meet in the future. All of these frameworks carry the exact same core mental models. You're still writing React components. You're still fetching data. You're still managing state. The difference is just where and how the framework decides to execute that code. Now, you already heard me talk about hydration errors and use client and vendor lockin. So, instead of repeating what's wrong with other frameworks, let me show you how tanstack start actually solves these problems. It comes down to four things. Client side first, but fully server capable. So your components render on the server and hydrate in the browser ready for state and event handlers out of the box which means that you're not forced to manage strict client server boundaries everywhere. So no use client directives on top of your component files or use server on your server actions. Your react components are just react components. Server work happens at the route level not inside of your JSX. also type safe routing from database to component because it's built on tanstack router. It automatically connects your database types on the server straight to your react code on the front end. So if you change a database query, your editor will instantly show you a red line if something breaks. It's perfectly type- safe end to end and you don't get that from Nex. js by default. Let's also talk about SSR, streaming, and built-in RPCs. The framework sends HTML to the browser instantly, so the user sees the page fast. And when you need to write data back, like saving a form, you use simple server functions. These functions validate your data automatically and keep everything type safe from front to back. No separate API layers and no TRPC installation. It just comes builtin. And you can also deploy it anywhere because it's built on vit which means incredibly fast dev server startup and hot module replacement. But it can also be deployed anywhere where JavaScript runs. Versel, cloudflare, netleifi or just a plain and node server with no platform lockins. Does any of this sound completely new? I mean at least some of it should ring a bell. But even if it feels a bit overwhelming right now, don't worry. I'm going to break every single concept down step by step in the next lessons. So the big takeaway right now is that Tanstuck start exists for a very specific reason. It was built to solve the architectural friction and the developer experience headaches that come with building applications in any other framework. An industry is already placing its bets. Major infrastructure players like Cloudflare, Netlefi, Clerk, and Neon are actively partnering with Tanstack to make it a firstass way to build for the web, which means that you're learning exactly the right tool at exactly the right time. So, let me show you how all of this actually comes together.
The Request Lifecycle
Let's trace exactly what happens when a user types your URL into the browser and hits enter. I know you came here to write code, not listen about HTTP, but this is the one mental model that explains every weird bug you'll hit later. Why your loader ran twice, why your data showed up on the server but not on the client, or why the hydration broke. 10 minutes here saves you hours in the build. So, let's go. The browser sends an HTTP get request to your server. That request hits the tanstack start server which is actually running node under the hood. Start looks up at the URL, matches it against your route tree and figures out which routes are active for that URL. Let's say the URL is skills tanstack query. Start sees that this matches a route with a dynamic param and that it lives inside of a parent layout route, both of which are active. Now start runs the loaders. A loader is a dedicated data fetching function attached to a specific route. It runs exactly when the URL changes, fetching the data required for that page before the React component even begins to render. The loaders are where you talk to your database, call your APIs, or do whatever work is needed to produce the data your page requires. Start can run loaders in parallel if they don't depend on each other which matters for performance. I'll go deep on loaders in the upcoming lessons but for now just understand that they run on the server. They run before rendering and they produce data. Once the loaders have resolved start renders your react component tree on the server. It calls your root component which renders the layout which renders the page all the way down. This is normal React rendering just running in node instead of a browser. The output is an HTML string. That string contains your actual content, the skill title, the description, whatever your loader fetched already added into the markup. That HTML then gets sent to the browser. The browser paints it immediately, which means that the user sees your page and not a spinner or a blank white screen. They see the actual content and that's exactly what SSR is and why it exists and then after hydration your app behaves exactly like a client rendered React app. From here navigation to a new route doesn't go back to the server for HTML. Tanstack router intercepts the link click runs the next routes loader as an API call updates the React tree and the browser never does a full page reload. The user gets fast app-like navigation. The first load got the SEO and performance benefits of server rendering which means that you're getting best of both worlds. So one thing to keep in your head as you continue is the word server in this context means the node process that start runs not a separate API server. It's not a different codebase the same application just running on two different surfaces. And if this is still a bit blurry, don't worry because we're going to explore it in practice throughout this course. So, let's continue.
Installation
Okay, no more theory. Let's dive right into creating our Tanstack start project. Head over to tanstackstart. com where you'll see this simple landing page. At the top right, there's the start and then try it in 60 seconds. This will be the fastest way to get a start project up and running with the CLI. On the side, you can also see that Tanstack start has partnered with tools like Code Rabbit, Cloudflare, Netifi, Neon, Clerk, and more. Many of which I'll also teach you how to use within this course. Now, I've zoomed in so you can see this a bit better. And let's simply copy this installation command. Open up your code editor and the terminal within it. Then paste the command mpx tanstack/ CLI at latest create. Press enter and it'll ask you to install the tanstack CLI. So simply say why. Go ahead. Now it'll ask us a couple of questions to set our project up. First is the name of our project. Since we're just at the crash course part of the application, I'll call it tanstack start CC as in crash course. Then it'll allow you to select a tool chain that you want to use. go with biome which is a new way of linting and formatting as it replaces both eslint and prettier with a single rustbased tool one configuration for both and it's faster than those two combined now it's going to ask us whether we want to include a demo and example pages they're amazing if you want to poke around to see what tanstack start is capable of but for now you have me to lead you through all of it so you don't need it so simply say no next it's going to ask us about add-ons that we would like to add to a project to start with. Later during our skilled application build, we're going to immediately start with tools like clerk for authentication as well as post hog for product analytics, session replay, and feature flags. And it's amazing that these can just be baked into it as soon as you start your app. For now, since we're just in the crash course part, I only want to include one, and that one is compiler, the React compiler. Typically, if you wanted to stop a component from rerendering unnecessarily, you had to manually wrap functions and use callbacks or use memo hooks. But the React compiler automatically analyzes your code and applies these memorizations under the hood during the build step, which allows your React code to stay clean and simple, but run with more performance. So, always turn this on by pressing the space key and then enter to go to the next step. Later on, we will initialize our skilled application with a GitHub repo, but for this crash course, you can say no. The CLI will now quickly generate all the files and run mpm install automatically to grab all of your dependencies. You don't even have to run the mpm install command yourself. Once that is done, simply cd into your project and then run mpm rundev to run a local v development server and hold command key and press this localhost 3000 to open it up in the browser. You should be able to see something that looks like this or maybe it looks a bit different on your end. Either way, tanstack start gives us type- safe routing, server functions, streaming by default, and is Tailwind native. All of which I'll teach you how to make the most of throughout this tanstack start course. Alongside running this app, I also want you to open it up within your code editor. So find this repo in your files and drag and drop it right here. Then you'll be able to see all the files and folders generated by the starter, which we can explore more deeply in the next lesson.
Project Structure
Okay, let's talk a bit about the file and folder structure of a tanstack start application. How does routing work? How does layout work? and how everything else connects. Let's start from the bottom to the top and into the vit. config. ts file. This is a good place to start because it tells you how the app is built and served. Specifically, this vit config loads the tsconfig paths. So, aliases for files like add/ something actually work. It also loads the Tailwind CSS plugin as well as the tanstack start vit plugin which is the framework integration point that hooks the starts routing and runtime behavior into vit. And finally it loads the react plugin that enables react compiler babel plugin. Then if we head over into tsconfig. json this file tells typescript how to understand the codebase. And it looks like this base URL got deprecated in the latest version of TypeScript. So for now, I'll simply tell it not to flag this because the tan text start put it there. So I don't want to mess with it. But this will likely get updated in the upcoming versions of Typescript and Tanstack start. Some other things that we can see right here is this include part which says which files should be part of type checking. In this case, only the. ts ts and tsx extensions will be checked. There's also a couple more interesting settings like this strict true which raises the safety level and catches more mistakes early. There's also this module resolution set to bundler which makes Typescript resolve imports the way vit expects instead of using the older resolution behavior. The no emit set to true means that TypeScript is only being used to check types while Vit handles the actual build output. This base URL that we checked right here and the paths make shorter imports available so that you can do add/ components and then just type in your component name instead of referring to the full path. and then types set to vit client adds specific types that are useful for clientside assets and vit runtime behavior. Basically, this file defines how Typescript reads the whole project. Then we have the good old package. json which gives this project its name, marks it as private, sets the type to module so that we can use the modern import export syntax and under scripts we have a couple for running the development version of the app. the build, preview, test, format, and more. And then the dependencies section contains runtime packages like React and Tanstack start while the dev dependencies contain tooling packages like biome, vit, vest, and typescript. Package log. json keeps the exact versions of packages, but then biome is the new interesting file. This file controls the formatting and linting. It specifies which files to lint and also includes a set of recommended rules such as the tabs and the indentation style. But basically this file replaces the usual eslint plus prettier setup. And it looks like it's getting the schema from biomejs right here where it has all the default configurations. Then there's a get ignore to not push the node modules over to GitHub. The CTA. json JSON which is an interesting one. And this one records that this project was created with file-based routing, TypeScript, Tailwind, Biome, and React compiler. It also records that mpm was used as the package manager and whether git was initialized or not. It's not part of the runtime app, but it helps explain the starting shape of this repo. And finally, the source folder is where the magic happens. It contains the styling system, the router setup, the route files, and the shared components. And if you're building features, this is where most of your work will happen. First, we have the styles. css file. This is the foundation of the styling for the entire application. It includes imports for different fonts and Tailwind CSS, which is going to be used for writing all the files. And you can see the initial theme and colors have already been set up for this tanstack start. But of course, we can clean all this up and make it work with our application. Then there's the router. tsx file. And this file creates the tanstack router instance that the app uses at runtime. It imports the route tree from the generated route file and then passes it into create tanstack router. Scroll restoration set to true gives navigation a more natural browser-like scroll behavior, while the default preload set to intent tells the router to start preparing routes when the user seems likely to navigate them. Pretty crazy stuff out of the box. There's also this default preload stale time which keeps the preload behavior fresh instead of assuming the old preloaded data is still current. At the bottom there's this declare module which registers the router type with the tanstack router. So the rest of the app gets proper typing information. This file turns generated route metadata into a working router. There's also this route tree. gen. ts which is a file automatically generated by tanstag router. It imports all the routes and turns them into typed route objects like this. then connects them into one route tree and generates TypeScript types for different route ids, paths, and two values so that we can actually reach those routes. In this repo, it currently describes the root route plus the forward slashabout route. You shouldn't edit it manually because Tanstack will regenerate it. Its job is basically to sit between the route files you write and the router that runs the app. And finally, there's the routes folder, which drives the filebased routing of your tanstack start application. You'll write all of your routes as files or folders within this folder. Starting with index. tsx, which represents the homepage. That is what you can see if you head over to the forward/ route. Then there's the about which shows you the about page. And finally, there is the underscore_root. tsx tsx page which is the most important route file in the project because it defines the root route and the shared HTML shell. You can see it right here. So if you need to modify something on the initial HTML document or body, you can do that here. Oh, and this theme right here, theme init script basically runs before hydration and sets the correct theme early, which avoids that white flash of the wrong color. I hate when that happens. So, it's super nice that this is just built in right here. Finally, there is the components folder, which includes some shared components like the footer, header, or theme toggle. We'll add our own components later on. And then, there is the public folder, which holds all the static files. The robots file tells the search engine crawlers what they're allowed to access or not. There's the manifest. json, which describes this app as an installable app. So, you can technically install this on your phone. And then there's a favicon as well as two different logos. Oh, and if you also see a tanstack folder, this is simply some temporary data that tanstack uses while the app is being developed and node modules. Basically just contains all of the packages from the package json which includes react tanstack packages tailwind and more. So that's it. Now you understand the file and folder structure of a tanstack application. And in the next lessons you'll learn to work within this whole project and make it yours.
Components & Styles
Now before you write any framework specific code let's understand the most important thing about tanstack start and that is that your components are just react components. Sounds simple right? But let me explain what I mean. At no point in time will you have to add the use client or use server directives at the top. If you already know React, then you already know how to write components in tanstack start. So back within our application, let's remove the current UI code that is within our source routes index. tsx. We basically want to have a clean starting point. We'll keep the imports and the exports of the route and then we'll just have a regular main function right here. Then back into the browser, you'll instantly see the changes saying hello world from tanstack. Now let's also delete all the other components from the components folder including the footer header and the theme toggle and remove them from the root. So we can remove the imports as well as their references right here before and after the children. If you go back now, you'll be able to see just the piece of text that we have right here alongside some styling coming from our styles file. This thing on the bottom right are our tanstack dev tools which we can explore later on. They're a part of our root. If you want to get rid of them, simply remove them right here. But either way, they're not going to be present in production. Now, let me show you how to create a new component. You just have to go to the components folder and create a new file. And let's call it skillcard. tsx. It's just a regular tsx file. Within here, you can run rafce, which creates a react arrow function component with ES7 module system. Basically, just this. And if this command didn't work for you, you can head over to extensions and install React snippets. There's multiple versions of this package. You can choose whichever you like. I currently have the ES7 plus React Redux React Native snippets, but maybe I should have updated to this newer one. You know what? Let me actually do that right now. This one works similarly, but in this case, we can use the sfc command to create a stateless function component. So, you can just run sfn C, which automatically creates this component with props. Or if you want it without props, just sfc is going to be enough. and then you can give it a name and return a div within it. This skill component is something we'll use later on within our skilled application, but basically it's going to be a card that's going to display a specific skill like in this case tanstack start and it can be within an article because it's a card as well as a p tag within it. Now we can render that card within our index. So head over to our routes index. tsx tsx. And right below our hello world, we can render a ul, an unordered list with an alli that's going to have the skill card, which you can import and render right here. So, if you go back, you'll be able to see a single skill right here. I can zoom it in so you can see it a bit better. And now, let me show you how we can improve this skill card component by adding props and its own state. First of all, we can declare some props by adding a new type at the top of the component called skill card props. And it's basically going to be an object that's going to have a name of a type string. This is simply how we define a type in TypeScript. Nothing related to tan stack start. Then we can make this component except a prop called name and it's going to be of a type skill card props. We can then create a new use state by saying const liked and set liked to figure out whether we have liked this specific card. And that's going to be equal to use state at the start set to false. And we can also keep track of the number of likes by saying likes is equal to if we have liked then it's going to be one else it's going to be zero. By the way, let me know if these things right here on the right are confusing you a bit. These are just different warnings that we have in real time which will get fixed later on as soon as we use these actual functionalities. But if this is confusing you while I type, just let me know right now so I can remove it in the upcoming video. So now right here within this article, we can render a P tag that says skill. Beneath it, we can render an H2 that renders the name of the skill. And then finally, we can render the number of likes right here, such as likes. And then if number of likes is equal to one in that case we'll simply say one like else we will say number of likes plural like this. Finally we can render a button below that's going to have an on click functionality which is going to take the set liked state setter take a look at its current version. So whether it's liked or not and then it'll simply reset it. So it'll set it to the version opposite of the current version. Either like or dislike. The type of this button will be a button. And within it, we can simply render some kind of a heart icon coming from Lucid React. It's going to be a self-closing component that's going to have a fill set to well depends on whether it's liked or not. So if it is liked, in that case it'll be current color. else it's going to be none and we can also set the size to about 18. So now we have created this very simple skilled card component and to be able to use it properly we have to pass a prop to it. So right here from the index you can see that property name is missing right here. So we can just add a name set to something like tanstack start. And you know what we might as well render a few more cards right here. So we'll do the second one of Typescript and let's do another one of MongoDB. Of course, you can add the ones that you prefer. So now if you check it out, you'll be able to see well what is supposed to be a card. At least it'll become a card once we actually add uh styles to it. But yeah, we have three different components being rendered right here on the screen. Each of them managing their own state. So what am I trying to tell you right here? it is that in tanstack start there's nothing different than from what you've learned in ReactJS already at least when it comes to components and if you want a refresher you can check out our React 19 full course on YouTube which is still super relevant and it'll bring you up to speed with understanding how well React works so that you can then leverage it better in Tanstack but yeah just remember components in Tanstack start always run in the browser so whatever ever you have here, it'll run in the browser. They also run on server during SSR, but they have no special server capabilities, no database access, no environment variables, nothing beyond what a browser component can do. All the server work will happen in server functions called from loaders or mutations, but the components stay pure. But of course, the components matter for UI and UX, for actually presenting something to the user. So, we have to make them look good. And to do that, there's a couple of different ways to style our components in Tanstack start. You can use pure CSS, which leans on standard React inline styles, or set up CSS modules to keep your designs safely scoped to individual components. Because the framework is powered by VIT, it handles all of these traditional approaches without making you configure complex bundlers. But even better, by default, you get Tailwind CSS installed and ready to go within your project. Why? Well, why not? Because it's what absolutely everyone is using right now. From OpenAI building out their web interfaces all the way to Ferrari designing their site. When it comes to moving fast and building scalable UIs, no other styling framework even comes into the picture. So, we'll be using Tailwind to style our application. Let me show you how. Back within our skilled card component, we can give this article a class name set to feature-card. And this feature card is an already existing style coming from styles. css, which was created for us when we set up the tanstack start project. Remember when it showed the functionalities of what tans text start can do? Well, we'll just reuse those styles for now. Let's also give it the island shell class name as well as rise in and rounded-3 XL as well as a padding of five. Again, most of these components already exist within these styles. We're just going to be reusing them to show you how easy it is. And then we're adding some new ones like rounded 3XL, which just adds the rounded corners or padding. If you need a refresher on how Tailwind works, I also have a great quick full course that tells you everything you need to know about it so that you can more easily style your apps, including your Tanstack start apps. I'll put the browser side by side with our editor right now so you can see the changes that we're making live. Now, within this article, let's create a new div that's going to have a class name set to flex items- start justify between and a gap of four. And within it, I'll also create another div that'll have a class name space-y-2 to create some vertical spacing. Then within this inner div, we can put the skill, the h2, and the p tag. All of it is going to go here. And then we can put this button within this other div, the one that's going to nicely enclose it, but below this div with vertical spacing. Let's also style this P tag that says skill by giving it a class name set to island-kicker, a class name right here to this H2 set to display-ashtitle text-2XL and font-bold. And if you're wondering why it's mentioning some kind of shells and islands, well, that's because Tanstack is island themed. So, the style names are also island themed. Of course, later on we'll write proper class names. Then to this P tag that displays the likes, I'll give it a class name set to text-sm text dash and then in parentheses dash c ink soft. This means that it's using a custom CSS variable from styles. css because these colors are defined as custom properties, not regular Tailwind theme colors. But later on if you define it just as a regular Tailwind color you'll be able to use it more easily like text dash c ink soft like this without these additional parenthesis. But for demo let's keep this approach. Finally, let's style the button by giving it a class name set to inline- flex size-11 items dash center justify dash center rounded- full border dash in parenthesis dash line like this bg dash again a double line surface dash strong text dash again in parenthesis d- C in like this and a shadow of MD. And of course, we can zoom it out a bit because I was super zoomed in. And you'll see these cards are going to look a bit better. And finally, for the heart, we can give it a class name instead of the fill. And if it's liked, we'll set the color to fill dashcurren, which just makes the heart filled with the text color. And we'll give it a text dash. And once again, we'll use this lagoon deep variable. Else, we'll simply not provide any styles. Now, we can head back over to our index. That's going to be right here to add some spacing in between the cards. So, right here to this ul, I'll give it a class name set to margin top of six, list-ashnone, padding of zero, and space-y-5, which is going to provide that nice spacing. And now you can see how this UI looks in action. We have working functionality by managing different states and the number of likes on each card. And you can see that each card is handling its own state. And yeah, I get that these class names can be a bit confusing and they might change later on as the tanstack starter code changes. But that's totally fine. Even if this doesn't look anything like it does on my screen, the same concepts still apply. And later throughout this course, we'll dive deeper and implement our own styling. Still, as I said, if you want a Tailwind CSS refresher, go ahead and watch this YouTube video and then come back. But yeah, if you take a moment and think about what you've learned so far, you'll notice that it's nothing different from what you've already learned in standard React. We're managing local state by using the use state hook and attaching the onclick handlers to the buttons. No client server React components like Nex. js, GS just pure React. So now it's time to dive deeper into actual Tanstack start features.
Routing
So far everything's been mostly the same as you already know it. But routing is where Tanstack start model differs from a plain React app. And not only the routing itself, but every other feature that we're going to go over, such as loaders, layouts, server functions, and error handling connects directly to routing. So the core idea is that your files become URLs. Basically, file-based routing. You already know that. So what you have to do is create a new file inside of the routes folder. And within it, you have to export some kind of a route object. This part right here. Then tanstack will register it automatically. The file path will become the URL path. And as I mentioned before, every tanstack start project has one special file named underscore root which renders for every URL every single page. It's where the main HTML document lives, where persistent navigation and footer go, and where global CSS files are imported. But of course, the next page is going to be the homepage, which has its own special name called index. tsx, which simply points to the forward slash. You can see this forward slash right here, which points to the URL that this file handles. And forward slash simply means the homepage. Now, in our project, you can also notice that we got the about. tsx page pointing toward/about. So, if you head over into your browser and head over to localhost 3000/about, you'll see a little about section. So, let's create another route. It's going to be contact. tsx inside of the routes folder. And check this out. We didn't even have to run some kind of a snippet definition or write any code, but Tanstack start automatically creates a route template for you and registers that route. So the only thing you have to do is visit localhost 3000/ contact and immediately you'll be able to see this new route. You can also create nested routes in tanstack. Simply create a new folder within the routes folder and in this case let's call it skills. Within skills you can create a new file and you can call it new. tsx. Automatically it creates that route and you can visit it by heading over to localhost 3000/skills/new and you can see that you get a hello from skills new. And if you just want to go to for/skills, you can do that too by creating a new index. tsx file directly within it. So now you can also head to localhost 3000/skills. And this index file within the skills folder will act as the homepage for for/skills. You can think of it that way. But your application also needs dynamic routes. For example, in this case, a skill detail page would be something like forward slashsklls/typescript or slklls slashtanstack start or any other combination of different letters. But we can't create all of these files at once at the start because our application can allow for dynamic skill creation. So somebody can come in here and write an Nex. js GS version 2035 for example and we don't yet have the file for it. So how can we make this work dynamically? Well in Tanstack router if you prefix the file name with a dollar sign it'll be a dynamic route. So check this out. Under skills create a new file start with a dollar sign and give it a skill ID name. tsx like this. It'll automatically create it. And you can see that it filled up the path skill ID which will become a dynamic variable. So now if you head over to localhost 3000/skills slash like anything you can type literally anything right here. We want to get access to this skill ID variable which you can do super easily. The only thing you have to do is dstructure it by saying const dstructure the skill ID or whatever else you named this file. And that's going to be coming from route use peraps. So we're just using this route that was created for us. And now we can say hello from slkills slash skill id. And if you reload, you can see forward/skills slash anything. Similarly, you can change it over to tanstack start and it'll be dynamically updated. And you can also have more than one dynamic segment. For example, a URL like slash users slalice slkills slreact-hooks where both the username is dynamic as well as the skill name is dynamic too. But you have to follow the same approach for creating that file and it will be stored in a place like this. Routes users and then it has to be a dynamic username Alice then skills is constant and then you can have a skill ID which is again dynamic. So let me give you an example of that. I'll just create a new file to show you how nested dynamic routes work. Under routes I'll create a new folder and I'll call it users. Under users, I'll create a new dynamic folder this time which is going to start with a dollar sign of username. Then within that folder, I'll create another folder called skills. And within skills, we'll create a new file starting with a dollar sign to indicate it's dynamic called skill ID. tsx. Now within it, we can get access to both the username and the skill ID, which is going to look something like this. And we can just nicely display it on the homepage by saying username skill is the following skill right here. So now you can head over to localhost 3000/ users/alice skills react hooks and you can see that Alice skill is react hooks which means that we are dynamically accepting both of these URL params. That's the basis of how routing works in tanstack start. You have dynamic route. Everything starts with file-based routing. Then there's this root and different index pages. And you can have nested files and folders. But maybe you're building a dashboard that needs a sidebar apart from the footer and the header. Or like a checkout flow that needs a stripped down layout with no navigation. Or maybe authentication pages need a centered card. You can solve all of this with nested layout routes with shared UI. And to implement that layout route, you have to create a special file named route. tsx in that folder. A file named route. tsx provides a layout for all routes inside its directory without adding a URL segment on its own. So create a new file right here under routes and create another folder called dashboard within it. Within this dashboard, create a new route. tsx file. And let's add some dummy elements within it that are going to represent the sidebar. So instead of returning just a div, we can return some kind of a main tag with an aside that says sidebar. And then you get access to this outlet element which you can import from tanstack react router. This basically means whatever the page is being rendered right now but this route is acting as its layout. So now we can create the homepage route for this dashboard by simply creating a new index. tsx page. And then within the dashboard, we'll also create another one called skills. tsx and another one representing the settings. tsx. We typically have all of these things within a dashboard. So now if you head to localhost 3000/dashboard which is the homepage you'll see that it has the sidebar at the top and we're rendering the forward/dashboard route. But if you go to for/ dashboard slout you won't see anything as that is what a shared layout is all about. You get to have a common place with a shared UI such as this sidebar right here but that route doesn't add anything to the URL. Now for a second I want to bring back the tanstack devtools. It was right here in the root file at the bottom of the body. It looks like this. Tanstack devtools config with the position bottom right and the tanstack router added as the plugin. So now if you head over to the for/ dashboard and press this little icon on the right, you'll be able to see all of the different routes but not the route. tsx route. So now if you navigate over to a different page, let's say we go back to the homepage. Now you can see that the dashboard layout isn't turned on. But if you navigate to dashboard for/skills, you'll be able to see that the dashboard layout is on. So we can see the sidebar, but also what the skills route is displaying. You can further explore these dev tools such as you can see the different social previews if you share this website. There's the settings and then here you can also see which routes are being matched. And if you click on one of these routes, you can see some more info about how specifically is it getting matched. For now, I'll just close it. That's it. I think we've covered about 95% about routing in tanstack start. If you explore the docs, you'll notice that there is also one special type of routes which you're going to use much less often. So, if you want to explore that, go ahead and check out the docs. Or even better, download our free Tanstack ebook down in the description. Oh yeah, and while we were learning and creating and renaming these different route files, Tanstack Router was generating this route tree. ts. This file gives Typescript all the knowledge about your routes. It's the source of type safety for components like link, use, navigate, and use params. In development with the VIT plug-in, it regenerates automatically every time you save a route file. never edit it manually, but do commit it to the version control. So, now that you know everything there is to know about routing, let's take a look at how we can actually fetch data within those routes.
Data Fetching
Inside of a traditional React application, you would probably write a standard fetch request inside of a use effect hook. The component mounts, the hook fires, and the user stares at a spinning loading wheel while they wait for the network to connect. We call this the render fetch waterfall. The browser has to download JavaScript, parse it, boot up React, render the layout, and only then does it realize it needs to grab data. Tanstack start completely eliminates this problem using something known as route loaders. A loader is a dedicated asynchronous function attached directly to your route file. When a user clicks a link to visit a new page, the router intercepts that click. So before React even attempts to render the next component, the router fires the loader to grab the data. So by the time the component actually renders, the data is already sitting there waiting. So the user gets an instant transition with zero layout shift. But let's make it make sense within the code. Head over to your source routes index. dsx which is our homepage. And let's define a loader within the route creation right here where we create a new route. You can also define a second key value pair within this settings object next to the component. There you can add a loader and you can make it into an asynchronous callback function. For now, let's simply say console. log loading data for the forward slash route like this. That's it. You've just added a loader. So, open up the homepage, check your terminal, and reload. And you'll see the loading data for the for/out console log. But instead of console logging, let's actually call the famous Pokemon API and render its results on the UI. It's a super simple free API that you can get to by heading over to pokei. com. So let's simply copy this URL to clipboard. And right here, let's use a simple fetch request by saying con response is equal to await fetch. And we're going to make a fetch request to this URL we just copied. We can even store that URL above so it's a bit easier to read. So I'll say const pokey API URL is equal to this URL right here. And now we can just make a fetch to this Pokey API URL. But let's not go with this single Pokemon. Let's fetch all of them. Then once we make a request, we can actually parse the response by saying data is equal to await response. json. JSON then we can add a console log saying loader data and then we can actually console log the data and return that data right here. So now to be able to access the data from the loader the only thing you have to do is go within your component or route and say con's data is equal to route use loader data and that's it. If you've done that you can just very simply use that data. So, I'll comment out these cards and right at the top below the H1, I'll render a UL with a class name set to margin top of six, a list set to none, padding of zero, and space-y. And then within it, I'll map over the data by saying data. res. m map, where we get each individual Pokémon. And for now, we're going to specify that they contain a name of a type string. And we want to automatically return an LI, a list item. I'm using parenthesis here, not curly braces, which means that we are doing an instant return. I'll give it a key since we're mapping over it. That's going to be Pokemon. name. And within it, we can just render a skill card. And we can pass the Pokemon name for it. So I'll say name is equal to Pokemon name. And now you can see a list of all of these different Pokémons that we're fetching from an API. This is pretty amazing, isn't it? We're fetching this data directly within the route itself in such a simple and intuitive way. Keep in mind that this loader runs before the component renders. So whatever you return here in the loader is available in the component via the use loader data hook. That's it. Super simple. But Tanstack makes it even easier. In the same route, Tanstack allows you to define pending states, components for error, for not found, and even allow you to validate your request using ZOD before even making a request. All attached to your route file. So let me quickly show you how that works. Then we'll dive a bit deeper into it while you're developing the skills application. And if you really want to learn how Tanstack works under the hood, grab that free ebook as part of the link in the description because with it, you'll also join the weight list for the full Tanstack course that I'm actively working on. So let's first talk about pending states. See, when you fetch data, there is always a delay. And instead of showing a blank white screen until the data arrives, you want to show a loading spinner or a skeleton UI because tanstack router handles this beautifully with pending component functionality. So right here above or below the loader, you can specify the pending component and then define it right here by returning it. Let's say that it is a div and this div will have a class name of p14 text-c center and it'll say loading Pokemon. Now, in an ideal world, this wouldn't just be the text. It would be a skeleton component that nicely shimmers and specifies that something is about to come on the screen. And even better, you can also include the pending MS. So if your data loads incredibly fast, like under 1 second by default, it won't flash that jarring loading spinner on the user. It'll only show the pending UI if the network is actually slow. So you can say something like 300 milliseconds, which means that it'll wait 300 milliseconds before showing the loading state. So now you know how you can display some kind of skeletons while the content is loading. Then we also have error handling. So for any errors happening in that route, you can define a separate specific or reusable component that renders when the error does happen. Again, you can do it on top or below, but basically you do it by defining the error component in the same way as before. And you can render some kind of a div that has a class name set to p4 text- red 500. And you can display some kind of an error message like P tag. that says oops error happened. So now within your loader let's say that we simulate an error by throwing a new error and then say API is down. If you do that you'll see oops error happened. But how can we be a bit more specific about which error happened? Well, to do that, you can accept the actual error object within the error component. And we can also get access to the router functionality by saying use router right here. But make sure to make this not an immediate return rather just a regular function and then return this div manually. And make sure to import the use router coming from tanstack react router. What this allows us to do is to specify which error happened by getting access to its error message such as oops API is down. Maybe we can make this a bit less threatening of a color. There we go. And we can even render a button right here that has an on click that simply calls the router and says invalidate. This forces the loader to run again from scratch. And we can make it say something like try again. There we go. So now we have this button as well. And if we go ahead and remove this throw new error, we again see the loaded data. Similarly, you can also handle 404s. So let me collapse this a bit. And right below it, I'll render a not found component prop which also accepts a new function that returns a new component. In this case, a div that has a class name set to P14 text gray 500 and says nothing found here. So now within your loader, if you go ahead and throw a not found which is coming from Tanstack router like this and save it, you'll be able to see a nothing found text right here. Maybe we don't have to give it a text gray. We can see it just fine like this. There we go. Nothing found. And we can only render this if there's no data. So I'll add an if statement right here and check if there's no data. s or if data. res. length is equal to zero. And only in that case do we actually want to throw this not found. Of course we want to do that after we actually fetch the data. So now in this case you can see that we get back our data. But we know that within this single loader and the actual not found component and error component, we're handling the loading and the error states. Oh, and Tanstex start also allows you to ensure that you're passing the right data in the first place before making the request by validating it through ZOD. That's also something we're going to explore later in the course. You can also implement caching, stale time, preload time, and many other things. All of which we're going to dive later within this course and the ultimate Tansta course on jsmastery. com. But for now, you know enough to know about how you can fetch data in Tanstack start. And although here we're fetching the data for a normal route, it works in the same way for nested and even dynamic routes. Now, we can fetch this API right here because it doesn't require any API keys. But if it did, you wouldn't be able to put those keys right here because during the initial page load, the loader will run on the server. But when the user clicks around your application like a standard single page app, the loader will execute directly inside of the browser. And because of that, you absolutely cannot put secret API keys or raw database queries inside of a loader. If you do, that code will get shipped to the client browser. So to fetch the data securely, your loader must call or use a server function. And that's exactly what we're going to learn next.
Server Functions
next. A server function is a piece of code that is guaranteed to execute strictly on the server. So when you call a server function from your client code like inside a browser run loader or a react button click tanstack automatically transforms that call into a proper HTTP request behind the scenes. It acts as an RPC remote procedure call. But what the heck is actually an RPC? Well, in simple terms, it means you can execute a function that lives on a completely different computer like your server exactly as if it were a normal local function in your front-end code. You don't have to write any manual fetch requests, define URL endpoints, set headers, or parse JSON responses. The framework handles all of that messy HTTP plumbing automatically while keeping your TypeScript types perfectly intact from back to front. And if you've been around the React ecosystem, you've almost certainly heard about the term TRPC. It's a massively popular standalone library designed to give developers this exact type- safe RPC experience. But the beautiful thing about Tanstack start is that it has this TRPC style architecture built natively into the framework via server functions. So you get this incredible developer experience and endto-end type safety of TRPC without having to install or configure a separate library. So back within the code, let's create a new folder right here under source and call it server. within it, create a new file called Pokemon. ts. And here we'll define our secure backend logic. We'll start by exporting and creating a new get Pokemon function which we're going to create by using the create server fn coming from tanstack react start. You can define the method of this function which I'll pass over as get but it can also be a post and then we can define the handler for that function which is going to be an asynchronous function that makes the same request we've made before directly within our homepage. So if you head over into the index under our loader we can basically copy this part about the response and the fetch and paste it right here. So we're saying con response is equal to let's also not forget to get access to this pokey API URL. So we have it here and then we get the data back from the API. In this case we don't have to work with this not found rather we can just console log something like executing a secure database/ API call on the server dot dot like this. just so we know that this is happening from the server. Then it's going to get the data and we're simply going to return it here. If you want to, you can also add another console log saying data successfully fetched on the server. So once that is done, we'll simply return the data. So now go back to our homepage, which is our index. tsx, and update our loader to use this new server function instead of fetching directly. We no longer need to confuse this component with the API data fetching logic like this API URL nor with all of this fetching. So I'll simply remove all of this and say cons data is equal to await get Pokemon function coming from the new file we created. This almost feels like magic because this file right here will get executed on the server, but we're getting access to the data right here on the front end. So now if you head back over to the browser, you'll be able to see the same list as we got before. And if you navigate over to the dashboard and then check your terminal, you'll be able to see that we're getting the logs from the server, not from the client. You can know that because this is a log, typically coming from a secure server. So, what you've just learned was creating and using a server function to read data or in this case make a get request to another API or in the future your own database. But server functions are not just for reading data. They're also heavily used for mutating data like creating, updating, or deleting records using the post request. And doing that is also super simple. The only thing we have to do is define a post method to a server function. So let's do it right here below the get Pokemon FN. We'll create another one called export const save favorite Pokemon FN which is going to be equal to create server function with a method of post because we actually have to submit some data into it. And you can define the handler just as before where we're going to pass in an asynchronous function where we can define what's happening on this specific function. In this case, we can also do some kind of a console log. Instead of executing a secure database API call, we can say something like saving data to our secure database. And then we can create a new promise like await new promise which when resolved will simply call some kind of a set timeout because in this case we're mocking the actual time it takes to reach a database. And finally once we save it we can simply return a success of true as well as saved. And we want to save some kind of data. But how do we actually pass the data into the function? Well it's just a regular function. So you accept it as a param into this function. Here we can dstructure the data that we're passing into it. And Danstack also allows you to add a validator. So you just have to doinput validator right before and then provide what kind of data you're accepting right here. In this case, we're accepting a name of a type string. So we just want to get the name and return it. And then you just continue chaining the dot handler on it. That looks something like this. So just like you validate URLs in your loaders, server functions have built-in validation. It ensures that the data the client sends matches exactly what the server expects before the handler ever runs. So you can easily plug ZOD in here for strict schema validation. And that's it. That's how you can create both get and post server actions. And you also learned that you can call the get server functions directly within the route loaders. But how do you use a post or other method server action inside of a React component? Because when you call a server function inside of a React component, you're essentially doing what's called a mutation. And that would typically happen something like this. I'll create a new route right here under the routes folder and call it favorite. tsx, which is going to be our dedicated page for favoriting different Pokémon. Inside of it, I'll create a form because typically you always have form when you're submitting some data. I'll call this route component a favorite page. And I'll first make it accept two different use state fields. The first one will be set to name and set name. So we can choose the name of the Pokemon we want to favorite. At the start, set to an empty string. And then the status. Have we already favored it or not yet? and set status. Then we want to extract a stable reference to the server function for React to use. That's going to look like this. const save Pokémon is equal to use server fn coming from tanstack react- a simple way to use these functions. And then now we're going to pass over the favorite Pokémon fn coming from this file where we actually created it. Then let's define some JSX that we can display in the browser. I'll render a main that's going to have a class name set to page wrap, padding x of four, a padding bottom of 8, and a padding top of 14. And right within this main, we'll render an H1 that says save uh Pokemon. And I'll render a form. Within this form, we want to render an input. And this input will have a couple of different fields such as a type of text, a value set to name, and an onchange set to the setter of the name, where we get the event, which is the key press, and then we're setting the name to be the event target value, which is the actual key press. Then I'll give it a class name of border as well as padding of two and rounded. And finally, a placeholder of something like Pikachu. And of course, we also need a button that's going to submit our form. So, it's going to be a button of a type submit with a class name of BG Blue 500, text white, padding of two, and rounded. And it'll simply say save. Oh, and below the form, we can also display a P tag with a class name of margin top of four. that's simply going to display our state of the status. So now if you head back over to the browser and head over to the favorite and then reload it, you should be able to see save a Pokémon and then a form that we can submit some data into. Let's finally hook up our save Pokemon server function to our form by declaring a new onsubmit that's going to call the handle submit function which we'll create now. And let's give it a class name of margin top of six and space- x of 4. So the last thing for us to do is to declare a handle submit function which is going to be just a regular asynchronous callback function that's going to accept the event. In this case that is the form submission event. So that's going to be react. vent. We're going to immediately prevent the default uh behavior of the browser to reload the page. Set the status to something like saving because these actions take time. And then call the server function directly exactly like calling a local function by saying await save Pokemon and then pass over data set to the name. Finally, we can update the status to something like successfully saved. And then we can enter some kind of a name right here. So, let's make this a template string. And then we can also reset the name field back to an empty string. So, now if you head back into the browser and type in something like Bulbasaur, at least I think that's one of the Pokemon, and let's click save, you'll see saving, and then successfully saved. and you've just saved it on the server side. Sure, this action is just mocking a real API call for now, but we didn't have to spin up our own Noode. js server to be able to do that. Tanstack start is a full stack framework that allows you to call your own server functions that you generate directly here within the same codebase. This should help you understand the complete architecture of Tanstack start because now you know how to define different components, how to route in between different pages, how to fetch data using loaders, and how to securely cross the network boundary by using server functions.
API Routes
If server functions are so amazing, then you might wonder why would I ever need to write traditional API endpoints? Well, for 95% of your application's internal data fetching and mutations, you won't be writing them. You will use server functions. But there is that 5% where server functions simply do not work. See, server functions are strictly designed for your front end to talk to your backend. But what happens when an outside service needs to talk to your application? So you need a server route or a traditional API endpoint in a couple of different situations. The first situation is when you are receiving web hooks because a service like Stripe or GitHub needs a public stable HTTP URL to send a post request to when a payment succeeds or a codebase updates or when you're building a public API. you want to let third-party developers fetch data from your platform using Python, Go or a standard REST client. Or another situation is when you need to return non-JSON data. For example, when you want to generate a dynamic XML sitemap, serve a downloadable CSV file or return a dynamic image. Server functions are expected to return JavaScript objects. API routes on the other hand let you return raw web standard response objects. So whenever you need a raw standard HTTP endpoint, Tanstack start provides server routes. So let's build one. Server routes follow exactly the same file-based routing conventions you already know. So let's create a public endpoint that anyone on the internet can hit to get a greeting. create a new file right here under source routes and within a new folder called API. Let's simply call it hello. ts. Tanstack as usual will write or define that route for you. But in this case, we'll remove the component related code and keep rest as is. So you can remove this component reference from here. And now the only thing you have to do is develop the API route or endpoint. Inside of these braces, you can say server and then define different handlers on that server. In this case, we'll define a get route which is going to be an asynchronous function that destructures and accepts a request and then returns something. For example, I'll console log someone hit our public API and then you must return a standard web API response object and that looks something like this. return response dot JSON because in this case we're returning JSON at least but you can also return other things and then within it we'll simply pass over a message of hello world and in the second parameter we can provide the headers they're going to include the cache control like this that's going to specify that this is public with an S max age of 60 and we can also define the is control allow origin and set it to an asterisk which means that people will be able to access it from anywhere. In this case, we're not even using the data coming from the request. So, we can just remove it from now. So, now back within the browser, if you head to this URL, you'll see raw JSON output. And that's your API endpoint in action. But you don't only need to call this within the browser. You can also use any kind of an API tool like Postman or similar to make calls to it. So, do you have to create a separate folder named API and put all of your server routes there? Well, that's the standard convention that everyone uses to not mix the front end and back end. But you don't have to put your API routes in a separate API folder. The same file can define both a serial route and a UI route. For example, you can also define a regular React component right here in the same component. I'll call it hello component, but in that case, it would make sense to switch this over to a tsx file so that we support actual JSX syntax. You don't have to write this with me, but you can just follow along to get the idea. Or if you want, feel free to pause your screen and copy this out right below this server declaration. What we're doing here is creating a button that manually fetches its own routes post handler. So it's basically making a post request to the same endpoint and providing some data to it like we're calling it externally. So let's actually accept that name change directly here within the handler through the request data. We're going to get access to the request and then say const body is equal to await request. json. And then we can return the response in the same way we did before. by not saying hello world but instead by saying something like hello and then we can use the body. name that we're passing into it. But how will Tanstack router know to refer to this as the get but also to render this hello component on the page when somebody hits the hello route? Well, the only thing you have to do is below the server, you have to define the component that renders right here, which in this case will be the hello component. And now it would make more sense to actually put this outside of the API folder because it doesn't just act as an API but rather the full part of your application which is also going to change the URL to just a hello route. So somebody making this get request right now would need to just go to forward/hello. Again make sure you created the file route like this and then added the server as well as the component like this. And then let me show you how it works. Back on the localhost 3000 forward/hello, you can now click say hello and then it says hello tan stacker which basically means that on the same endpoint we defined both the component as well as a server post route and then we're consuming the output of that API call directly within here and setting it to our state. Hopefully this makes sense and again we'll dive so much deeper into API routes and everything that they can do within the full course. So you can go ahead and check it out already and continue this course you're watching right now with added lessons on jsmastery. com. There I'll show you how you can also accept different programs within API endpoints as well as how to use this special middleware object. But even with this you have a pretty solid understanding of how server actions components and now API endpoints work within Tanstack.
Metadata & SEO
With the skills you've gotten so far, you're already able to develop a fast datadriven application with secure server functions and beautiful UI. But if you're building a public-f facing app like a blog, an e-commerce store, or the agent skills platform, none of that matters if users and search engines can't find your pages. So in a standard single page application or a spa, search engine crawlers and social media bots like when you paste a link into Twitter or Slack often just see a blank index. HTML file with a title of react app tag and that's it. As everything about rendering happens after the user loads the page after JavaScript has been loaded and browsers as of now don't understand your JavaScript. And since Tanstack start supports serverside rendering or SSR, you can now write fully populated dynamic metadata directly into HTML before it ever leaves your server. I mean every app needs a set of meta tags and in Tanstack you define those right here within source routes and then root. tsx directly within the head. You can notice that we have a couple defined for us already, such as the character set, the viewport, and the title. So, let's change the title and the description of this page. We can already imagine that we're building our agent skills platform that we're going to dive right into next. So, we can change the title to something like agent skills, the developer platform, and we can also modify the description to something like discover and share top development skills. So now if you refresh, you should be able to see that the title of your application changed. And if you navigate to the about page, you'll see that the title is still the same. But what if you want to modify it on that page and you want to change it to something like about us? Well, to do that, Tanstack Router uses a system called nested dduplication. So if a child route defines a title or a meta tag with the same name or other property as a parent route, the child route automatically overrides the parent tag. So the only thing you have to do to change it is head over to a different page and right here under create route. You can define a new head property where you can return an immediate return. So make sure to wrap it within parenthesis. a new meta property that's going to be an array where you can define a title set to something like about us agent skills. And you can also add some kind of a description that's a bit more fitting for the about us page. So now if you go back you can see about us. So that's how you modify the static metadata within your tanstack start application. It's super simple. In the full course, I'll also teach you how to change the metadata dynamically, as well as to make sure that when you post your page over on Twitter or send it in a Slack or Discord chat, you have a nice title, description, and image. But yeah, that's it for the metadata.
Course Outro
Hey, congrats. You made it through the crash course. Give yourself some credit. When you click this video, you probably had no idea how Tanstuck start actually worked. Now you understand the request life cycle, file-based routing with nested layouts, why loaders kill the endless spinner problem, and the difference between a server function and an API route. That's a massive foundation. But there's still a gap between understanding a framework in a tutorial and actually shipping with it. And that gap hits the second you open up a real project. In the Tanstack Start Pro course, we go way deeper on the theory. This video couldn't fit. Type-S safe search params with Zod route masking for Instagram style modals. Streaming in slow queries, serverside O middleware, caching strategies, testing, and production deployment. Every pattern you'll actually hit in a real build. Then we put it all to work by building skilled, a full stack AI agent marketplace from empty folder to deployed app. Firebase, Post Hog for Analytics, the full stack. The rest of this video continues the build here on YouTube, but this is the perfect point to jump over to the platform and keep it going there instead. Deeper lessons, full project, and a better learning environment. So, click the link to the pro course below. It's 40% off for the next 72 hours. And let's go build something amazing.
Installation
and we are ready to dive right into developing our great application. Let's start by setting up a new tanstack start project by running mpx add tanstack/ cli at latest create and then just run enter. It'll ask you whether you want to install the tanstack cli to which you can say y yes and then it'll ask you for the name of your project. In this case we're building skil our AI agent skills marketplace. So press enter. It'll ask you for the tool chain you want to select. In this case, we're going for biome, which is a combination of eslint and prettier. And it's asking us whether we would like to include demo and example pages. Since you're following along with this build with me, you definitely don't need the demo. Then it'll ask you for the add-ons that you want to add to your project. You can select them by pressing the space key. And in this case, we'll go with clerk to add authentication to our application. Then we'll go for the react compiler as well as shaten for styling and query to integrate tanstack query into our application. Once you select those four that's clerk compiler shaten and query press enter and it'll ask you for the clerk publishable key. So let me show you where you can get it which you can get by clicking the clerk link down in the description and then creating a free account. Before we create an account though, I want to head over to pricing to point something out. Clerk recently modified their pricing plans and they actually increased what's available in the free plan. No credit card required, unlimited applications, and 50,000 monthly retained users, which is significantly more than what you might need for a demo project and still more than what you need for an actual SAS application that you want to launch. So, go ahead and sign in. You can use any method that you prefer. Once you're in, you can create your new app. You can see I have plenty right here, but today we're building skilled. You can choose which signin options you want to add, such as email, phone number, username, Google, or any other of many social providers you want to use. In this case, I'll stick with email and Google. And if you scroll down, you'll get your clerk publishable key right here. So go ahead and copy it and paste it right here into your terminal and press enter. It's also asking us if it would like to initialize a new git repo, which of course we want to do to be able to commit and push our changes so that we can track them and also get our code reviewed by code rabbit. But more on that soon. For now, let's initialize it with a new git repo. And as soon as it gets initialized, tanstack start CLI will also install all the dependencies needed for you to run the app via mpm. Very soon the app will be ready. So what you can do next is simply drag and drop that newly created folder into your code editor so that we can explore the code as well as run mpm rundev to actually spin up the project on localhost 3000. You can then press commandclick to open it up. So let's clean it up so we get a nice starting point for our application. You can do that by heading over to source routes index. tsx which is our homepage and deleting everything from within the return statement and only returning a main that's going to have an h1 that says something like hello from tanstack start. Other than cleaning this homepage up we can also clean up the about route by completely deleting it. It's going to be right here under routes about. So just delete it. Also delete all the components in the components folder because we're going to create our own. And finally modify the source routes root to remove the footer and the header and keep it straightforward where we just have an HTML document. Within the body, we just have the children wrapped in a clerk provider. We can also clean up the body class names by removing this selection right here, keeping the first two class names and then adding wrap anywhere as well. And while we're here in the root, we can also modify the head metadata right below the viewport where we have the title. You can rename it to something like skilled the registry for agentic intelligence. And we can also add a description by adding a new object that has a name of description as well as the property of content set to something like discover, publish, and operate reusable agent capabilities from a route driven workspace. This definitely wasn't written by AI for me. But yeah, now that we have the metadata and we have removed all the boilerplate code from the app, let's focus on the styles of the application. This is just an idea for the design that our designer came up with. But basically, we're going to have some kind of cards that each represent a new agent skill. Those cards later on will be able to be upvoted. We can comment on them or you can save them, filter them, or search across them. And instead of you having to go here and color pick all of these different versions of black and purple and noting down the paddings, border radiuses, and so on, I'd much rather have you focus on learning tan stack as much as possible. So for that reason, I'll give you the initial stylesheet that we can use for the app, and then later on, we can also add additional styles. So head over into your style. css where we have our tailwind CSS directives as well as some default tan stack start variables. and we want to replace it with some new variables that look a bit like this. You can find this full styles. css file in the video kit link down in the description. Simply go there, find the styles, and then copy and paste it right here. Tailwind CSS in tanstack works exactly the same as it works in Nex. js, React, or even plain HTML. This file contains some reusable utilities and components used in the application. So if you want to learn how to style your applications effectively, then you can watch our completely free Tailwind CSS full course where you can learn how to use it in detail so that it's easier to understand how it works across all of these different frameworks. Tanstack start included. So once you put these styles right here and head back to localhost 3000, you should be able to see a very simple black background with a white text that says hello from tans text start which means that we are done with the setup and we're ready to start focusing on the layout of our application.
Layout
application. Now that we have a blank slate ready in the browser, let's actually create a layout around which we'll develop our application. So create a new component under components and call it navbar. tsx. Within it, we can initialize a new react functional component. I installed this simple react snippets extension which allows me to just type sfc to create a new stateless function component. So you can try it out as well or you can just write it manually and then call the component navbar. It's not going to take in any props right now and it'll return a simple div that's going to say navbar. Then let's import it within our root because we want it to show on almost every single page. So right here above the children you can simply import the navbar component. And right here we can also create the rest of the layout. So within clerk provider go ahead and create a div. And that div is going to have an id of root layout. Then within it create a new HTML 5 semantic header component. And within the header you can render a div with a class name called frame. And then within it you can simply put the navbar which we just imported. And then below the header, you can create a main that's going to encapsulate the rest of the content with a div within it that has a class name set to frame. And then within this frame, we can render the children. Below it all, we can still keep these Tanstack dev tools in case we need to debug something later. So now back in the browser, you can see that we have the navbar at the top and then there's the main portion of the screen right here. Also on the left and the right we have nothing. So our app is nicely centered. So let's develop the navbar by heading over into it and turning it into an HTML 5 semantic nav tag. And we can keep this as an instant return, meaning just wrap it in parenthesis so that we don't even have to explicitly say return. Then give this nav a class name set to navbar. And within it, create a div that has a class name set to brand. And then within brand, create another div that has a class name set to mark. And then within mark, create another self-closing div that has a class name set to glyph. So now, if you save this and go back, you'll be able to see our logo appear right here. That's because we're using these Tailwind classes. So, if you search for any of these pieces of text within your application and head over into the styles, you'll be able to find all the styles that are being applied when you apply a class name of navbar or brand for that matter or mark or glyph, which in this case just applies this nice logo without even using an image. It's just plain CSS. And that's the logo of our application called skilled. So right below the mark we can render a link coming from tanstack react router that's going to point to the forward slout which is the homepage and then within it you can render a span that says skilled. So if you go back it'll look something like this. Then below the div encapsulating this link we can render another div with a class name set to actions. And then within it we can render another link that has a two forward slash sign-in slash dollar sign like this. And this link will have a class name set to btn primary. And within it we'll render a piece of text that says sign in. Now you can see immediately that I get an error right here saying type signin is not assignable to type dot or slash. That's because it cannot find any additional routes besides the home route in our application structure right now. Signin route doesn't exist. And that's the beauty of tanstack start. It knows when a route doesn't exist and it tells you so. But soon enough we'll create that route. So don't worry, we'll just keep this as a placeholder for now. Also, right next to our signin piece of text, we'll also render a sign-in icon. So go ahead and import log in coming from Lucid React which I believe is already installed for us with Danstack start. So before typing sign in you can also render a self-closing login icon that's going to have a size of 16. And if you head back that's going to look something like this. Now what we want to achieve next is separate the navbar in an interesting way from the rest of the content. Take a look at these two little X's on the bottom left and the bottom right of the navbar. I'm going to zoom it in so you can see it a bit better. They divide different parts of the app. And if you zoom out even more, it'll all make sense. So, how do we implement them? Well, back within the code, I added another component called crosshair. It's just a single SVG which you can get in the video kit link down in the description called crosshair. And then once you put it right here within a component, you can head over within the root and then put it right here below the navbar. You can put two, one for each side of the navbar. The reason why all of this works by default is because of the styles within the frame. So it's taking a look at where this frame is and then it's automatically positioning those SVGs to be at the right spots. If you want to learn how to do these kinds of tricks with Tailwind CSS, go ahead and check out that course. I'll also link it in the description. But yeah, now we have the navbar and at least some kind of a layout on top of which we'll later on place all of our components. And while we're still working on the setup, I want to make sure that while we continue developing new features, our app doesn't start looking something like this where it becomes a misindented mess. For that reason, we have to make sure that our linting actually works so that you and I together can keep our code bases clean. We already have biome set up, but it doesn't work by default. You also have to install the biome extension. So, head over to extensions and search for biome. And you should be able to find it right here. Tool chain of the web. So, just go ahead and install it. Trust it. And if you reload your entire window and you make some changes and it's still not working, well, in past times, that would mean a lot of debugging and documentation reading. But nowadays, what I would just do is open up any kind of a chat agent and just ask it for the fix. So I would go ahead and tell it linting with biome isn't making changes on save within this tanstack start application. I have a biome extension installed, but the changes are still not happening on save. Can you look into it? And then press enter. And if you're wondering how I was just able to speak into it and it wrote it down all correctly, I was using Whisper Flow, which allows me to well basically speak with AI agents. And why would I do that? Well, it's because it's faster than typing. I don't type at a slow rate of 45 words per minute. And neither do you. You're most likely much faster, but speaking is still going to be faster than the fastest typing out there. So lately, I've been finding myself using my voice a lot when speaking with AI agents. And no, Flow is not paying me to say this, at least not yet. It's just what I use when I code, and I wanted to share it with you. So, let's let our agent check what's the problem and fix it for us. I'll go ahead and allow it to run some commands. It confirmed that it's actually picking up the changes, but it's not updating it on the screen. So, it's asking us to modify our settings JSON to turn on the format on save option. So, I'm going to allow it to make that change for me. It edited it and it'll now retested. Oh, there we go. It worked immediately. I just made a change. Press command S to save it and everything got formatted immediately. And we even get an explanation of what the agent did. In this case, biome was set as the default formatter, but format on save itself was not enabled for the workspace. For you, maybe it was something else. But either way, our AI agent will figure it out and allow us to get back to what matters, which is learning and actually developing our app. So, now that we have the base layout done, and now that our linting is working, let's actually push this over to GitHub. You can head over to github. com/new and create a new repository. I'll call it something like skilled and just go ahead and create it. And then we can make our first commit. I'll copy this command by first opening up a second terminal by splitting it on the side. We'll use this one for running our app and the second one for pushing changes. Then I believe our project has already been initialized. So the only thing we have to do is add all the changes by saying get add dot get commit-m first commit then get branch m main to switch over to the main branch and then get remote add origin to connect our local repo with the one published on GitHub and then finally get push u origin main to push all the current changes over to the main branch. So, if you reload, you'll be able to see your Tanstack start application right here on the internet. I'll go ahead and modify the about section by changing the description to the same thing that we have right here within our root. That's going to be a description of discover, publish, and operate reusable agent capabilities for the website. We can put this one later on when we deploy it, but for now I'm going to go ahead and put the link to the best educational website for web development, which is going to be jsmastery. com, of course. Then you can add some topics such as tanstack start and then remove these from the homepage because we don't need them. This will just end up cleaning your workspace just a bit so that we can continue pushing to it. What we want to do next within this terminal is switch over to a new branch by running git checkout-b to create a new branch and we'll call it dev where we'll be committing and pushing all of our changes to. So that in the next lesson when we start developing the homepage we can actually open up a pull request to merge it over into main. And like real developers before we push it we're going to get an actual code review by code rabbit so that we know that it's safe to merge. But yeah, let's dive right into building our homepage.
Home Page
Let's get started with developing the homepage. Back within our text editor, let's head over to the index route. Since we already have a main in the root, we can now just make this a div and give it an ID of home. And since we will now be developing the homepage, we want to see the changes that we're making live. So for that reason, let's show the browser side by side with our editor. We can look at it in the mobile view and then later on if we need we can expand it so we can see how it looks like on desktop as well. Within this div I will render a section that has a class name set to hero. So that's going to be the hero section. And then within it we're going to have another div that's going to have a class name set to copy which is going to include the copyrightiting text for our page. So that's going to be an H1 that's going to say the registry for and then we can do it in a new line. So I'll put a break right here. And then still within H1, but below this break, we want to render a span that's going to have a class name set to text-gradient. And there we can say aentic intelligence like this. So now we can see the registry for agentic intelligence. And below this H1 we can render a P tag that's going to say something along the lines of a high performance registry for procedural agent skills. Discover, publish, and operate reusable agent capabilities from a route driven workspace. Or you can make it say something shorter and more meaningful. But yeah, let's head below this P tag and below the div still within the section and create another div that has a class name set to actions. And then below the actions, we want to include two different links. The first link is going to point to forward slashkills. So that's going to be a new page that we have. And let's make sure to import this link coming from tanstack react router. And immediately you'll be able to see that the two skills doesn't yet exist. But don't worry because we're going to create it later on. If this error is bothering you like seeing the errors right here on the right side, please let me know down in the comments because this is something I'm still experimenting with. For the time being, I will open up the settings and say toggle inline message from the airline package which is going to hide it for now. I think this is a bit cleaner, but again, you let me know down in the description. So, now we can hover over it and see what the error is actually all about. Okay, great. So, now within this link, uh, we'll have a class name and it's going to be btn primary and right within it, we're going to render a terminal icon coming from Lucid React with a size of 18. And right below it, we're going to render a span that says browse registry. Perfect. This is already looking very good. I think I'm too zoomed in for this mobile view. So, if we zoom out a bit, it's going to look even better. And then we can duplicate this link right below. And instead point it to skills slash new. This is going to be a btn secondary. You always want to have just one primary action per page. And then we can simply make it say publish skill instead of browse registry. It's going to be just publish skill. Perfect. So you know that here you can explore other people's AI skills or you can also publish your own. Then let's head below this div and below the section and create another section that's going to have a class name of latest pointing to the latest skills. And then right within it, I'll render a div that has a class name set to space-y-2 to provide some vertical padding in between the elements. And then here within it, we'll render an H2 that's going to say recently created. And then within a span that's going to have a class name of text-gradient, we can actually make it say skills. So now it's going to look like this recently created skills. And then below this H2 we can render a P tag that's going to say something like latest skills loaded from fire store in descending order. Fires store in this case is our database but again that's going to be totally up to you. We can load them from any kind of a database as well. And finally below the speed tag and below one more div, we want to render our cards. So that's going to be a div with a P that's going to say skill card like this. And we want to have multiple of those by mapping over them. Now each one of these cards is going to have specific properties passed to it, specific props. So what we can do is head over into our application and create a new file called typed. ts which stands for type definitions. And here we can already start defining how each skill is going to look like. We're basically defining a type. And a more detailed type is an interface. So I'll say interface skill record because it's going to be a record in the database. And we have to ask ourselves what will it have? It'll have an ID of a type string. It'll have a title of a type string as well. A slug which is kind of like a name. So if we have a title called something like let's say uh write code like this then this one will simply be write-ash code like that just so it's URL friendly. Then we also need to have some kind of a description which is also going to be of a type string. Then we're going to have a category of a type string as well. A tags which is going to be an array of strings. So we can define it like this. Then install command which is also going to be string created at which is going to be a string or a null if we don't have it yet. Author clerk ID which is going to be the connection to the author that created that specific skill also going to be a string or null. And finally the author email which is also going to be a string or null so we can easily connect it. So we want to keep track of all of these different uh types so that our application knows how to act and behave and so that we don't try to access something like skill. est a variable that doesn't exist but we know that we can access skill ID and it'll tell us right here that it knows that it exists and that it has to be a string. No more guessing and much more predictable workspace. Alongside this single interface we just wrote together right now, there's going to be interfaces for other parts of our application and much more predictable codebase. So now that we have this type, let's go ahead and create a new component that's going to use it right within source and components. Create a new file and call it skillcard. tsx. You can use that shortcut either RAFCE or SFC depending on which one you're using and call it skill card. And for now, we can simply return a div that says skill card. Now, let's go ahead and consume that skill card and map over it within our homepage. So, head over here where we have the skill card and right within this div, go ahead and map over the skills. Now the question is where are these skills going to come from? So what we could do is just generate a random array right at the top called skills and then create a couple of different objects with the data for each skill. But this takes time and you're here to learn. So instead we're going to use AI to do it for us. open up any kind of an AI chat and then tell it something along the lines of create dummy skills with five data items following the skill type defined in type. d. ts file. So write something like this and we can be a bit more specific with providing it with the information it needs. So if you head over into that type d. ts, it's supposed to be right here. I think you can mostly just drag and drop it and it's going to understand what you mean. So, we're asking it to create that dummy skills array and press enter. It'll locate the skills. It'll read the reference to the skills page and where they need to be used and then it'll make them match the skill record shape so we can directly just map over them. There we go. We have enough context. It'll just create it under source lib so we can import it anywhere in the app. Pretty cool. And you can see how it is generating the changes right now. They're right here within lib dummy skills. And I'll say keep the file, close the chat, and we can explore it together. Dummy skills that matches the skill record array which has an ID, title, slug, description, category, tags, install command, created ad, author, clerk ID, and all of that other stuff. You can see how nicely it figured out what our app is all about and created five very realistic different skills. So, let's go ahead and actually map over those skills within our homepage because now we actually have the skills array. So, I'll open up a new dynamic block of code and say dummy skills. Make sure to import them. So, if it's greater than zero, that means that they exist. And then if they do, we'll simply return a div. We'll close it as well. And if there's no skills, we can simply render a p tag that's going to say no skills have been created yet. And then within this div, we can actually render or map over the skills by saying skills. m map. Currently, it's going to be dummy skills. m map. And for each skill, we can just automatically return a skill card component we created that's going to have a key set to skill id. And we can just spread out all the other skill properties. And let's also give this div a class name of skills dash grid so it can nicely space them out. So now you should be able to see five skills appear right here. But of course, currently they're just displaying skill card text. So the next thing we're going to do is going to be to dive into the skill card component and actually get it developed. First, we need to start from actually dstructuring all the props we're passing into it such as the author email, the category, the created at date, the description, the install command, as well as tags and title. So, these are different things we're passing into it. And we can define that of a type skill record so that it knows that author email for example is a string. And then we can return a new article. Article is like a div but it denotes that it encapsulates one specific piece of data. And that article will have a class name set to skill dashcard. And if you're ever wondering what these classes are doing, simply search them across the codebase. you'll find their reference within styles. css and see exactly which styles are getting applied. Once again, we can dive deeper into that within our Tailwind CSS crash course. But for now, I just want you to know that if you want to see what specific styles look like, you can always just find them in the styles. css and try to replicate them on your own. But yeah, we're basically now seeing these five empty rectangles that change the border as you hover over them. So now let's fill them up with content. We'll start with actually uh adding an overlay, a link overlay coming from Tanstack React router so that once you actually hover over the card and click on it, it'll lead you to the skills page. Later on that skills details page, but for now just skills is fine. We'll also set the tab index to minus one because here this link is not a regular button. It's actually going to apply whenever you click on the whole card together. So we have to give it an area label which is going to be set to open and then we can render the title of the skill and finally give it a class name which is going to be set to overlay. This class will make it appear over the entire thing, making the card clickable and leading to the skills page. Now, below the link, we can actually render some decorative top elements, which are totally optional. That's going to be a div with a class of Chrome as in Google Chrome. And then below it, we can render a div with a class name set to Chrome-bar. And within lights. And then within it, a div that's going to be self-closing with a class name of light and then red. And then we can duplicate it. Light amber and then light green. And now we have these what appears to be like a little Chrome window on Mac OS. And we can also below the lights render another div that's going to have a class name set to host. And this is going to point to registry. sh which maybe is the domain that we're going to deploy this site on. So now we created a header for the card. Now below this Chrome we want to dive into creating the body of the card. So let's give this div a class name of body. And I just noticed as I type this window is pretty huge right here. Like I always want to make the text as big as possible. But when I make the text big also the windows get bigger. So maybe if we make it just a tiny bit smaller it'll still be good enough. But again let me know in the comments down below whether this is too small or just right. And then I'll do another div that has a class name of meta as in metadata. another div that has a class name set to author and then within it an image where we're going to render the image of the author. So that's going to be a source currently pointing to logo 512. png with an all tag of author avatar and a class name set to avatar and you'll be able to see some kind of a logo appear right here. Then right below the image, we want to render another div that's going to have a class name set to author copy. And here we can render a name such as Adrian for now. And we can also render when this was created. So P tag where we're going to craft a new date out of the created at property which is going to be of a type string. And then we want to turn it to locale date string like this. So we display it in a human readable format. Now below this div and below this div for the author we're going to display a p tag for the category. So simply render a class name of category and then display the category variable that we're passing as a prop. So this one is development code quality testing backend and optimization. Later on we can even have a filter for these different categories. Now below this div for the metadata we want to display a div for the summary. So give it a class name of summary and then below it or within it render a link pointing to for now again it's just going to go to forward/skills and it'll have a class name set to title link and we simply want to render an H3 within it that's going to render the title of the card. So that's going to look something like this. API contract builder, performance tuner, uh writing code and refactoring safely. And then below it, we also want to display a description. Again, this is going to be coming from the props that we pass into the card. And finally, we want to also allow the people to automatically install that skill. So right below this div that's above the description, we can render a div with a class name set to command. Then we want to render another div with a class name set to command copy. And then here we want to render a span element that's going to have some kind of a command symbol. So that's going to be just the greater than sign and then underscore which is typically how we denote terminals. And then below that we'll render a p tag. That's going to render the install command coming from props. For example, MPX skilled add write code or MPX killed add refactor safely and so on. Perfect. Now we also want to add a button that copies this to the clipboard. So, right below right here, we can write it ourselves or again, this is also a perfect example of where you might just want to get some AI's help because you would have to look into how to actually copy the text. And before, this used to be a Google search, but now I would just go ahead and open up the chat and it already knows where I am within the skill card. So, I'll simply tell it below the command copy div, add a button that's going to have a class name of copy. It'll have a copy icon within it and on click it'll actually copy the text of the command and just press enter. This was super quick for me to tell it. The directions were small and pretty clear and I believe it should be able to do that within literally a couple of seconds, maybe even as I'm speaking to you. Yep. Yep, it was able to do it. So now if we scroll down and check the changes. Yep, here they are. It just added one single button. Maybe you call this an overkill. Did I really have to ask it to do that? Well, not necessarily, but it definitely sped up my workflow. Type button class name copy then navigator copy to clipboard install command and then a icon that says copy. So now if you hover over here and click copy and try to paste somewhere here, you can see that it actually copied it. But we're missing some kind of an indication that tells us that we have copied it successfully. So I'll simply ask you to do that. This is great, but give me some kind of an indication that the text has been copied successfully. Maybe by exchanging the icon to a check mark and then removing it or something similar. And then I'll also press enter. Again, if you're wondering how I am speaking to the AI, I'm using whisper flow. And I find it to be super useful for when I want to flow in the conversation with the AI agent. Okay, so it added this handle copy right here, but also added the use state to track the change. So now when I click it, we can see the check mark and then it disappears knowing that we have copied it. This is perfect. And again, let me know in the comments down below if you would like me to use AI in more situations within these videos and not type everything by hand because honestly, that's how I am coding nowadays and I want to teach you my exact process. Of course, we're going to dive much deeper into that within the actual ultimate AI development course, which I'm currently working on. So, right now, you can just join the weight list. I'll leave the link down in the description if you want to check it out. But yeah, let's continue. Right below this button and below the div encapsulating it, we can render a div that has a class name set to footer. And then within this footer, we can render a div that has a class name set to stats. And within it, we can render another button with a type set to button, a class name set to upvote. And for now, we'll actually make it disabled because we haven't yet implemented the upput functionality, but we will get access to the arrow big up coming from Lucid React with a size of 16 and a fill set to the current color. So now we have this upvote icon and we can render a span of tags. length which is going to show us how many tags we have. But later on we can come up with another variable for the number of upvotes that we have. Also below this button let's render a div for the comments. So it's going to have a class name of comments. Then we can render an icon called message square which is going to be some kind of a message with a size of 14. This is how it looks like. Basically a comment. And then we can render a span that's going to check whether an author email exists. If so, we'll make it one. Else we'll make it zero. Again later on we can come up with a separate variable for tracking the number of comments. Now, let's go two divs down, still within the footer, and let's create a very important uh div that's going to have a class name of actions. Within actions, I'll render a link. And that link will for now be pointing again to skills. So forward slashsklls with a class name open and a title of open and then this specific title skill. Uh the span within the link will basically say open and then we can render the arrow up right which typically means going to another page. So I'll give it a size of 14 and save it. And now we can see that you can click on open and that'll actually open up this card. Finally, below this link, still within the actions, create a button that has a type set to button, a class name set to save, an area label set to saved state, and then for now it'll be disabled. And this button will simply render the bookmark icon coming from Lucid React with a size of 16. So later on, we'll also be able to bookmark a specific skill so that we can then more easily find it on our profile later on. Perfect. So that's it. You've just built this skill card and with it the majority of the UI of the homepage. So, I'll now collapse this VS Code and expand the browser so we can see it more properly. And check this out. You've just developed a nicel looking navbar with a hero header section with a primary and the secondary button as well as recently created skills where we can actually see who created it when which category it belongs to the title which actually leads you to the skill description. Then the code to automatically copy it or upvote comment open and bookmark buttons. This is already looking amazing. So now that the homepage is looking good and we've tested the responsiveness and so on, I still want to open up a PR for it so we can review it properly and see whether there's anything we missed. So for that reason, I'll open up the terminal, check git status to see which branch we're on. We're currently on the dev branch and how many changes we have right here. Next, go ahead and run git add dot to save all the changes. git commit-m implement homepage and skill card and run git push. Since this is the first time we're pushing to the dev branch, we have to connect our local branch to the one on GitHub. So instead run git push- setupstream origindev and then that'll push the changes allowing you to go back to GitHub and see the latest changes right here. not on the main branch but rather on this new dev branch and you can also see that it had recent pushes 22 seconds ago. Now before we actually merge a domain uh we want to go through a process of a PR or pull request review and for that we'll use code rabbit because we want to move fast but don't want to break things. So click the link down in the description and try it for free. You can sign up with any provider but in this case let's go with GitHub since that's where our projects are hosted. And then once you're there, you'll have to give it access to your repos. So simply click add repositories. Connect using your GitHub and give it access to all the repos or just this one. And then you can search for it right here. We called it skilled. And there it is. It's a public repo that Code Rabbit is already actively monitoring. And let's create our first PR. Very quickly, Code Rabbit will hop on in and it'll tell you that it's currently processing the new changes in this PR. It also gave us a little tip to only use exceptions for exceptional problems. Exceptions can suffer from all the readability and maintainability problems of classic spaghetti code. So reserve exceptions for exceptional things. Well, yeah, that makes sense. So let's wait until it reviews it. This one should be pretty simple. But even right away, it gave us a summary of what we did. We implemented new features such as the skill cards displaying detailed skill information including author details, category and description, installation command, copy to clipboard feature, uh redesign homepage with hero section and quick navigation and the recently created skills section. This PR is pretty simple, just four different files changes. So, we should be able to get a full review in about a minute. And while that's happening, I also want to explore something else. Code Rabbit also offers extensions for different IDEs. In this case, I want to grab the one for Visual Studio Code. So, go ahead and click install. It's going to open it up directly within VS Code. And let's just install it. To get started using it, we have to log in. So, just click the login at the bottom right. Authorize it and then once you do, just click open and it'll auto sign you in. And here there's an active PR that it noticed. So we can ask it to review all the changes. I won't do that right now because it already came up with a full review directly within our PR. But later on in the course when we implement additional features, we can get a much faster review of our files at the same time as we're working on them. So we can actually make changes before we actually open up our first PR. Okay. So in this case, we implemented the skill record type interface and the skill card and we get a full summary right here. The changes so far have been super simple, so no need to dive in depth into them, but we did get some major potential issues. In this case, under our handle copy navigator, it looks like the line 24 returns a promise, but lines 25 and 26 update the UI immediately without awaiting the result. So, if clipper permission fails or the operation is unsupported, the card incorrectly shows the copied state, providing false feedback to the user. This is actually good. And see this is the part of the code that we wrote using AI. So recently Code Rabbit did a research report on AI written code and they figured out that AI written code produces almost two times as many issues. So while it's useful to get Code Rabbit to check the code that we write, it's even more useful to check the code that AI agents wrote. Thankfully the fix is super simple. we get a piece of code that we can just copy. Head over to the skill card where we have this piece of code and then we can just override this handle copy with the new one by removing these plus signs. And here it actually waits to see whether it has been copied properly. There's another potential issue right here under created at field. It says that sometimes created ad can be null but a new date of null produces a misleading date. So what we need to do instead is maybe if it is null maybe say unknown date. So I'm going to copy this and this is going to be happening at created ad field right here. So I'll replace it with a new one that just adds the unknown date. And there's another potential issue telling us to define the skills and the skills new routes before linking to them because right now these pages don't exist. Yep, we're aware of that and we're going to add them in the upcoming lessons. So with that in mind, these changes look good and we can go ahead and push them. You can either do that using the terminal or you can just auto push via the GUI by providing a refactor commit and then sync code rabbit automatically asks us whether we would like to do a new review. For now, not needed. And we can just merge this new pull request into the main branch. Perfect. It has been merged and we're ready to continue developing our great application.
Authentication
application. With our homepage developed, just before we can publish our own skills and browse others, we need to sign in. Yep, this not found page that you can see right here needs its own functionality. But thankfully, implementing it is going to be super simple. We already use a tanstack clerk integration, which it added right here. But you can go ahead and remove this clerk integration from the integrations folder as it's using the clerk react package and not a newly created tanstack package. We'll use the newest one. So this video is up to date. And as soon as you remove this, you'll see an error. So if you head over to the root, you'll see that clerk provider doesn't exist. But don't worry about that because I'll show you how we can create a new clerk provider right away. If you haven't already, click the clerk link down in the description and then head over to your dashboard. Create your new project and select tanstack start. And now let's just follow the steps. I'll put it right here on the side so it's easier to follow along. And the step one is to create a new tanstack react app which we have already done. Then we need to install the clerk tanstack react package which you can do by copying this terminal command and then pasting it into your empty terminal window. Step three is to set up our clerk API keys. So go ahead and copy them from here and move them over to yourv. local where right now we have the vit clerk publishable key. But below it also paste these two new keys which we just copied. We use the vit prefix right here so that it becomes a tanstack start variable. Keep both of these versions as we'll use clerk on both the server and client side. Step four is to add clerk middleware to our application which grants us access to user authentication state throughout the app and it also allows us to protect specific routes from unauthenticated users. So we simply have to create a new file right here in source and call it start. ts. Then simply copy this block of code which simply starts a new clerk middleware instance and by default it won't protect any routes. And finally step five is to add the clerk provider to our app by heading over into the root of our application. And whereas previously we have imported clerk provider from the integrations clerk provider. In this case, we simply want to import it directly from clerk tanstack react start. And you can leave the bottom part as it is where we're just referencing the clerk provider right here. You can see that part remained unchanged. So now back on localhost 3000, it is back to working. And let's quickly head over to components navbar. And right here under actions where we used to have a link which I will comment out for now. We want to render a variable called show coming from clerk tanstack react start which is used to conditionally render content based on user's authorization or signin state. So if the user is signed in it'll show one thing. If it's signed out it won't show at all. So this show allows us to pass this prop. So show when and in this case we can use either signed in or signed out. In this case when we are signed out we want to show a signin button. So clerk makes this super simple. Simply say signin button and import it from the clerk package with a mode set to modal. So now if you click on it, it'll open up the clerk authentication either in a modal or as a redirect. And even though having this model makes it super simple to add O to our application and that is actually the way I've done it within Morphin which is the prompt builder SAS that I've been building. If you click sign in you'll see that we also use this custom instance of clerk in a modal view. But in this course I want to take it a step further and create a custom signin and signup pages. So to do that, head over into source routes and create a new route group starting with underscore for o pages. So instead of letting clerk open their hosted link, which happens by default, we want to create two splat routes. That's going to look like this. Create a new file called sign-in dot dollar sign. tsx. and also create another one which is going to be sign up like this also with that dollar sign. So what does this dollar sign actually do? Well, these are so-called splat routes and not regular sign up or sign-in routes. So in this case when OOTH or email verification call backs come back, they don't come back to just sign in. They typically come back to something like sign upward slsso call back. And then we have the actual information right here. But a normal route of forward/signin will only match signin, which just won't work. Whereas this one will, and it'll open up the right page. So a splat route matches all child segments, allowing clerk to properly redirect and finish the flow. So now under these two routes, starting with the signin, we simply want to render the clerk sign-in page. We can do that by returning a new section that's going to have an ID set to sign-in and then within that section render the sign-in component coming from clerk to it. You can provide some props such as routing is going to be set to path. The path itself is going to be just sign in. The sign up URL is going to be set to sign dashup. Don't forget the forward slash. And then also the fallback redirect URL is going to be set to just a forward slash like this. And we want to do the same thing for signup. Leave the route as it is. Don't change it. And here in the return, simply return a section that has an ID of signup. Import the signup component from clerk tanstack react start. and then set up the path, the path to sign up, sign in URL to sign in URL, and then the fallback redirect URL. And finally, let's modify the navbar so it actually points to these new pages. Instead of simply returning a sign-in button with a mode of modal, we'll bring back our old link pointing to the signin route with a class name of btn primary. And now when you click sign in, you'll be redirected to a custom sign-in page within our own interface. So if we expand it a bit, you can see that this looks very native. And don't forget that you can always further customize exactly how this clerk instance looks within your specific application theming. Okay, so let's actually sign in to see how that looks like by clicking continue with Google. Select your account and sign in. You can see it's loading and we get redirected back to the homepage. And no longer there's a sign-in button, which is good, which means that this one isn't showing when we're signed in. But how are we going to show the actual avatar? Well, it's super simple. You just use the show once again, but this time you show something when we're signed in. And what do we show? Well, Clerk provides us with a user button component that you render like this. Literally one line of code and you get your avatar at the top allowing you to manage your account, explore the security and log out, add connected accounts, change emails, change the profile photo and more. That's absolutely amazing. So if you think about it, in a couple of minutes we have successfully added complete authentication capabilities to this application where you have a sign button. You can continue with Google or literally any other oath provider or with just an email and when you sign in you get redirected back to the homepage and you can manage your account. This is the exact kind of speed that I want to move when developing applications in the future. What matters most is actually testing your product to see whether people are actually going to be excited about in this case consuming different skills or in case of morphin which is her actual production project getting access to the specifications that allow AI agents to become much more productive and develop visually interesting apps. So instead of wasting a couple of days or weeks on setting up O just go for Clerk. It's completely free with up to 50,000 monthly users. But trust me, if you do cross 50,000 monthly users on your SAS, you absolutely won't care about a 2 cent per month cost for each user as you'll be charging much more for the subscription itself. So with that in mind, we can open up a PR and merge our authentication to do the main or in this case, we can take a look at the code rabbit VS Code extension where it automatically picked up on all the changes we've made. And I'll now ask it to review all of them. It's going to start the review, analyze the changes, and let's see what it comes up with. And within literally a minute, the review is in. In this case, we're integrating Clerk authentication in a Tanstack React Start app. And since this was a super simple one, we followed Clerk's docs to implement it and use all the official packages. There are no issues detected in this review. So, we're good to go. besides one little nitpick comment where we need to remove the unused import signin button. So if we check it out right here back within our navbar before we used a sign-in button but we switched it to a link. So in this case it just tells us right here to remove it because it's imported but never actually used. You can now decide to ignore this or fix it with AI. But I think in this case, we don't have to be that lazy and I can just remove it myself. Which means that this issue is now fixed. All the changes are good and I'm ready to simply commit this over to the dev branch and potentially merge it. Perfect. Changes are committed. Authentication is done. So, let's move on.
Analytics
on. We've got authentication working. Users can sign up, sign in, and see the homepage. But right now, we have zero visibility into what they actually do once they're on the inside. What do they do on the homepage? Do they upvote any posts, leave some comments, bookmark specific categories, or explore these skills in more detail? And how many of them sign up but then never come back? Without analytics, I'd just be guessing. So, let's set up Post Hog to give us the answers. I'll leave the Post Hog link down in the description. So, you can click on it and it's going to open up this uh very unique landing page where you can click get started for free to create an account. Simply choose your data region and sign up with any of the providers. Then create a new organization and finally you'll see a screen that looks like this asking you what do you want to do with Post Hog. In this case, we want to understand how users behave. But later on, we'll also be able to watch session recordings or monitor errors or run AB testing. So, for now, let's just start with understanding how users behave, and it'll automatically pre-seelelect some products based on the goal. So, click go, which will lead us to this very unique installation. Post Hog's AI wizard will detect our framework, install the right SDK, and configure event capture automatically. It's going to autodetect it. So, copy this command and then open up your terminal and paste it. For now, I will simply run this command. It'll ask you whether you want to install the Post Hog wizard. And once it installed, it says, let's do two hours of work in 8 minutes. It automatically figured out our directory and the framework. So just press continue and it'll authenticate you within your browser. Once you authenticate, it'll start analyzing your project. That means reading your codebase and doing the setup for you. Instead of manually installing packages, creating providers, and writing event calls, the wizard will handle all of it. So let's give it some time to analyze our code. I gave it a full terminal so we can better see what's happening. And on the left, we can learn a bit more about what the wizard actually is. And now you can see what it is doing in real time. It'll generate an event tracking plan, set up the environment variables, install Post Hog dependencies, add Post Hog proxy, insert event tracking code, find and correct errors, lind the files, and then finally create Post Hog dashboard and insights so you can directly check what's happening within your app, and then finally write the setup report and a wrap-up. And all this should happen in literally a couple of minutes. So, I'll pause the recording while it's doing its thing, and I'll be right back. Once it's done, the wizard will also allow you to install Posthog MCP, which lets your AI agent query your Post Hog data directly. For example, you can just ask it which page has the highest drop off, and it'll answer directly from the actual analytics and then you can make further changes on the website to fix it. Install it as it takes just about 30 seconds. You can select the editor to install the MCP server for. In this case, I'll go with Visual Studio Code. And you can select which features to enable. I'll leave all of them enabled for now. And press enter. Perfect. That's it. Post Hog has been successfully installed. It's pretty cool that we didn't even have to tell it which events to add. Even though our app hasn't yet been finished, it figured out that we want to track how many users use the install command on a specific skill or when they open up a click browse registry or when they publish their own skill or when they sign in. So, it already created all of these events for us. Press any key to continue and press enter to keep the skills. That's it. We exited the wizard and you can see all these new files created for us. Most of these are just markdown integration files. What matters most though is the post hog setup report here. It gave us all the information about what it did. So feel free to give it a read. Other than that, it did a couple of modifications. For example, within our navbar where it imported post hog, it used the use post hog hook and then when people click on this link, it simply captures that event. So we know what the user clicked. It did that across a couple of other files such as the skill card. So if we head down right here, it imported use post hog. It got access to the post hog instance through the post hog hook and then it captures it when people click the install command button or when they open up a specific skill. So head back over to your app. Maybe we can rerun it by stopping it with Ctrl + C and then rerunning it. And now we can try copying this skill to clipboard or maybe navigating over to it by clicking open. Then back within the Post Hog installation process, you can see that the installation has been completed because it auto captured a specific event a few seconds ago. So click next. It'll ask us whether you also want to auto capture the front-end interactions like clicks, submits, and more. Definitely. We'll also enable heat maps and web vitals. Oh, and session replays allow you to see exactly what the user is clicking, so you can spot specific friction points and maybe improve your app's UX. So, I'll go ahead and enable it as well. I'll skip adding additional sources. And the best part is that there are two different plans you can choose from, both of which are completely free to get started with. The first one doesn't even require a credit card and allows you to add Post Hog to one project. you will never get charged and you're getting 1 million events, 5,000 recordings, 1 million feature flags, and more. Or if you want to immediately get a couple more projects, and unlimited pay as you go usage, you can then go for this plan. For now, we'll stick with free, you can invite your teammates, and you'll be redirected to your dashboard. I would recommend that you take some time and go through this quick start to actually figure out how does Post Hog dashboard work. But everything start with your first event. So I'll mark it as done because we already had ours. If you head over to activity, you'll be able to see that a user under contact. jsmastery. pro opened up a skill and copied the skill installation command. Even when I clicked a specific SVG and so on, but what matters most is that initially I was just a random user and then I got identified. So from that point onward, you're able to track what this user is doing on the website. So now go back to this create new insight page and you can either create a new trend yourself by figuring out for example in this case counting page views in this case we got five or tracking some specific events like how many skills have our users opened. But what I prefer is just asking Posthog to create insights for us. So you can just switch over from browse to this chat right here and you can ask it what's happening within your application. For example, I want to know how many users installed specific commands and how many have opened the skill pages and just press enter. Post hog by default doesn't know what our app is all about, but thanks to its wizard, it has attached specific event tracking. It figured out that it has the events that we're looking for, such as the install command copied and skill opened. It'll read the schema and spit out the data right here in front of us by creating insights on the spot. You can then choose whether you want to save those insights to the dashboard so you can check them out later on or you can come back every time to the chat and just ask Post Hog what's happening with your application. But yeah, it looks like so far only one unique user has copied an install command and it was MPX skilled add write code. So we can even read which are the most popular skills on the page and which skill pages have gotten opened. It was the right code skill low activity over the last 30 days, but all of this is completely new. So we'll have to track it as we go. I'll go ahead and open this as a new insight and save it to our dashboard so that later on once we come back to it. We can see it on this new dashboard which I'll start from scratch and call it skill opens and installations. And we can add this new insight that we just created. This one too, install copies by skill category. You're basically building your dashboard like Lego bricks with the data that matters to you. And then within the app dashboard, you have typical metrics like daily active users, weekly active users, growth, accounting, retention, and all the stuff that Post Hog captures by default without you ever having to add it manually. While what we've just done using Post Hog Wizard might seem like just a quick setup step, you just need to understand just how important this is in the real world. Because as a developer building your own products, maybe through agentic development or just building out an idea that you've had, you don't just ship code. You have to track everything because companies need to know if users are actually clicking on this new feature you have spent 2 weeks building or if they drop off during checkout and how often do they come back. So, what we've set up just now is just the foundation. Later on, we'll also check out web analytics, session replays, and more. And this matters because there are thousands of developers who know how to build an application, and they're all applying for the same jobs you are or building similar apps. But knowing how to build a product and track its performance, well, that puts you in an entirely different league. When you actually track and understand product usage, you're going to be treated more like a product engineer, which proves that you care about the business impact of your code and not just the code itself. And it's only going to get better because later on I'll teach you more advanced Post Hog features, how to interpret this data, and how to use it to build a product that people actually want to use. So go ahead and commit these changes by opening up your terminal and running git add dot get commit-m implement post hog and run get push. Perfect. Let's keep it going.
Firebase
going. Now that we have O, we are ready to add the database. In this app, we'll be using Firebase. So head over to firebase. google. com google. com and click get started in console. Then create a new Firebase project. Call it skilled. Accept the terms. Select the parent resource and click continue. It'll ask you whether you want to enable Google Analytics for this Firebase project, which I'll toggle off as we're using Post Hog to track everything in much more detail. And click create project. Once it finishes up, you can click continue. And under project overview, add a new app. In this case, we're building a web app. Once again, you'll have to register your app with the name skilled and click register. Then you'll be given an mpm command with which we can install Firebase as well as the Firebase configuration. We'll go through this together later on. So for now, we can just continue to console. Then back within the app, head over to our env. local. Here we have a couple of clerk variables. And right below we can add Firebase variables such as the Firebase project ID which you can find right here under your app. And the project ID is your project name plus some random characters. Then we'll need this app ID right here at the bottom. So go ahead and copy it. That's going to be our Firebase app ID. And finally, we'll also need the Firebase API key, which you can find right here under the setup and at the top of your Firebase config API key right here. So, just go ahead and copy it and paste it right here. And alongside these, we'll also need to head over to the service accounts and generate a new private key. So, generate it, which just downloaded it to your device. So go ahead and open it and then paste it right below under env. Here you'll see a couple of different things which you'll need to remove such as this object and type and the project ID which we already have. But what you want to keep is going to be a Firebase private key ID and that is this one right here. So you can add it right here. And then we'll also have another one which is the private key. So you can store it under Firebase private key like this and just make sure to include everything. You can even include the string signs right here. So it starts at begin private key. That's just a comment. And then it ends all the way right here at the end. You'll also want to create another one for the Firebase client email which is going to be equal to this one right here. And finally the Firebase client ID which is coming from here. With that in mind, I believe we should have everything. Let's start once again from top to bottom. There's the Firebase project ID, app ID, API key. Then there is the private key ID, the private key itself, and then finally we have the client email and client ID. So, now that we're done with the setup, in the next lesson, we'll learn about all the different types of databases and choose the one most suitable for this project.
Database Setup
Today, Firebase offers three different database engines. And understanding why we aren't using one over the other is what makes you a senior developer. The first type is a realtime database, a giant single JSON tree. It's ultra fast for simple state syncing like a typing indicator, but you can't do complex querying. So, if you want to find skills created by users with a specific email address, you have to download almost everything and then filter it out yourself. Then there's the cloud fire store, a NoSQL document store, which means that you're dealing with collections and documents similar to MongoDB, and it scales to millions of users. But there are no joins support. So if a skill needs to show a user's email, you either duplicate the email, risking stale data, or make two separate network calls. And then there's Firebase data connect. It uses cloud SQL based on Postgress and it allows for relational mapping. So you can define a user once and a skill once and then join them in a single query. And more importantly, you don't have to write SQL strings manually. You use GraphQL. Firebase then generates a typesafe SDK specifically for your app. So your TypeScript code knows exactly what data is coming back and this works super seamlessly with Tanstack start applications. So that's why we'll be using Firebase data connect within this course. So to set it up, head back over to your Firebase project and under what's new or in some other section you should be able to find data connect or you can just go ahead and search for it. Then click get started and it'll bring you to a schema generator. For now, we'll skip the schema and we want to connect it to a data source. So, you can use an existing CloudSQL instance or create a new Cloud SQL instance, which you get completely for free for 90 days with no cost. And then you can just create another one if you want your free project to keep running. So, I'll click next. You'll be given your Cloud SQL instance ID, database name, and service ID. And you can choose the location that is closest to you. In this case, I'll go with Frankfurt and click submit. Now, it's going to provision your database. And that's it. Then you can head within your codebase, open up a new terminal, and type mpm install-g, which stands for global firebase-tools. to install the Firebase CLI. After it gets installed, you can simply run Firebase login and it'll tell you that the Firebase CLI's MCP server can optionally make use of Gemini in Firebase. I'll go ahead and enable it. It'll also ask us to collect CLI and emulator usage and reporting to which I'll say yes. Then it'll authenticate you in the browser and you're in. So finally, you can run Firebase init data connect. It'll ask you a couple of questions such as are we creating a new project or using an existing. In this case, it's an existing one. Then we need to choose which one is it. Looks like I have two instances of skilled. So I'll have to check which one we're using. So back within skilled service, it looks like it is this one 73 CAe at least on my end. So you can select the one that connects to your data connect. Maybe it's going to be the last on the list. It's going to say that our project already has an existing service. So, it's asking us whether we would like to set up local files. So, we can just press enter. And then it'll ask us, do we want to install the agent skills for Firebase? To which you can say yes, which will allow your agent to know how to write Firebase code better. Next, head over to extensions. And let's go ahead and install Firebase data connect. I think there was a link right here as well. So, go ahead and install it. and we are ready to start modeling the data for our application. What does that even mean? Well, it's all about thinking in terms of entities and relationships. So, first identify the entities of the application you're developing. In this case, the first entity is going to be the person who creates skills. They have an identity which is the clerk ID and profile attributes such as the profile image, email, and name. And then the second entity is going to be the actual skill which is the core asset of the application. It has a title logic such as the prompt and the usage and some additional metadata such as the tags. Then we need to define the relationships between the entities such as one to many like one user can create many different skills and one skill can only belong to exactly one user. In data connect, you don't nest the user inside the skill. You create a foreign key. Data connect then handles this automatically when we define a field with the type of another table. For example, author is set to user. If you've used fire store, you would have created few fields like tags as an array of strings. While in strict relational databases, you could create a tags table. But data connect supports arrays natively in Postgress which is perfect for search filters while keeping the schema simple. So let's navigate over to a new file which is going to be right here under the folder data connect and then schema and schema. gql. This is where you define your source of truth or your database structure. So let's first create a table for our user. So I'll say number one is going to be the user table. Here you can type user at table. The primary key is going to be set to clerk ID. And then we can define which properties will the user have such as a clerk ID of a type string. And this one has to be unique. It'll also have an email that's going to be of a type string. Then it'll have a username of a type string. And finally, an image URL also of a type string. And since we're working in a GraphQL file, you don't have to add commas. Add two end lines. And now for the second table, we'll define it as the skill table. And let's ask ourselves, what does the skill have? So right here, we'll start with a type skill, which is going to be a table. It'll have an id which is going to be of a type uyuid at default and we can define an expression which in this case will be set to uid v4 like this. Next we'll define the relationship which is going to be a field of author like who created that specific skill and it's going to be tied to the user table that you see right here. Next, we can have the skill content, which is going to be a title of a type string, a description, also and then finally tags, which is going to be an array of strings like this. And if you're wondering what these exclamation mark means, it simply means that this field can never be null. Like, it must be a string or this one must be an array that must contain strings. without it means that something is optional. So below the skill content we can also provide well let's do some kind of technical data about the skill such as the install command which is going to be of a type string. We can also provide a prompt config as well as the usage example all three of which are strings. And then finally for some metadata which is going to be the created at field which is going to be set to time stamp at default with an expression of request time when it got created. So with data connect we can directly execute our code perform different mutations or query the database. So let's go ahead and create a new user through graphql queries with the data we've just selected from clerk. And while you can work with data connect locally by running an emulator command through VS Code, we'll go with working on production to see how things work. These two schemas you've generated are only here locally. Firebase cloud isn't aware of them. So if you head back over to the Firebase console, you won't be able to see anything right here. To push them over here, we have to deploy our schemas so data connect becomes aware of them. But before that, let's create a couple of sample queries using VS Code extension and then deploy them. If you're in VS Code, you should have this add data and read data buttons at the top of your code. It's pretty crazy how this extension works. If it's not there for you, make sure to install it. And maybe you need to reload your VS Code for it to show. Then simply click add data. And this will add the user insert mutation. So that now we can basically add users within our application. We can do the same thing with the skill table. Simply click add data and it'll create a new file allowing you to insert skills. We can do the same thing for reading the user table as well as reading the skill table. So after you have created all of these new files, simply open up your terminal and run MPX-y Firebase-tools at latest deploy- only data connect and press enter. While that is doing its thing, you can notice that there's going to be many different files generated in the codebase, but we want to ignore them within our deployed data connect. And you can do that right here under our ignore file where you can add Firebase and data connect right here. That's going to reduce the number of files to push over to GitHub from like 127 to it's still calculating. Well, we'll check it out soon, but for now reopen the terminal. And it's asking us whether we want to execute this SQL against this instance. So go ahead and say execute all. There we go. Deploy complete. You can just reload your page right here. And you'll be able to see the generated schemas right here in Firebase Data Connect console. There's the user with an email username and image URL as well as the skill which has the relationship to the user. Now head back over to clerk. Go to users. Find your existing user and click show JSON. You can copy it and put it as a text file within your codebase. I'll add it somewhere here right in the root of our application. and call it user. json. And we need to extract a couple of things from it. I'll zoom it out a bit. And I'll create another file called data. txt, which I'll put side by side with this user. json. In this data. txt file, we want to extract the user ID. So, I'll simply put it right here. We then want to get access to your email, which we should have somewhere in the JSON as well under email addresses. Then you want to get access to your username. It looks like it's empty for my current account, but later on we can change it to something like JS mastery. And finally, we also want to get access to the avatar URL, which is going to be this clerk image right here. So you can add it there. Now head back over to your schema by heading over to data connect schema. gql and let's add this current user to our database through the extension by clicking add data. And now we can simply get the data from our existing user. That's going to be this clerk ID, this email, this image URL, and finally this username right here. Then right here on top, you can click run production project. And it'll tell us that it's about to perform a mutation in production environment to which we're going to say, yeah, just go ahead. Here you can see that the data was indeed inserted. And similarly, we want to add the skills for that user. So, right here under the skill table, click add data. And we'll want to head over to our dummy data that was created for us. I believe it was right here somewhere under source and then lib. There we go. I'll also put it side by side with skill insert. We need to choose the ID. In this case, I'll just change the last number to like eight. The author clerk ID still has to be the same one from our current account. Created at can remain as it is. Description can be just taken from any of these skills. Then the install command, we can also just copy it. The prompt config, I believe we're missing right here, but that's going to be something like you are a professional performance optimization engineer. Then tags we can add something like AI agent. Let's also add engineering and so on. The title is going to be the performance tuner. And finally, usage example can be something like review the code and optimize it. Again, I'm just giving you an example. And then go ahead and click run production to add the skill to production. If everything is good, it should be successfully inserted. So this was all about running mutations, but you can also directly run queries from the database. You can do that by heading back over to our schema or to some already created files like skill read or user read. And if you just click run local or run production, you'll be able to directly query your database right here. Pretty cool, right? You can do the same thing for the skills by heading over to skill read and running it. And you can see the skills that have been added to our database. And of course, now that we've actually added that user and the skill to the production database, you can see it right here under data. Of course, if you actually perform a querying operation, which we can just get this user read code from, paste it here and run it and immediately you'll be able to see the data right here. So that's it for this lesson. We set up fire store data connect and essentially created the starting point for the data structure of our entire application. In the next lesson, we'll use the Firebase generated SDK to display all of this data back on the UI. So let's do that next.
Data Fetching
In this lesson, we'll write a couple of queries and call them on the UI side to display real data and remove this dummy data cards. So, head over into our codebase and where you have the data connect folder. Within it, you'll see the example folder. Rename the example folder to connectors. This is a standard practice when using data connect. Then, within it, create another file called queries. gql GQL that will get the list of skills with budenation and search functionality. Let's write it together. I'll say query get skills. We're going to accept a variable of search term of a type string defaulting to an empty string. And we're also going to get another variable of a limit which is going to be an integer by default set to 10. and data connect blocks all access by default. So in this case we're going to turn on O which is the directive that defines the authentication policy for a specific query or mutation. And you can see by default it's set to no access. So in this case we're going to set off to be level off public as well as insecure reason is going to be set to skills should be visible to everyone like this. And then we can open up this query right here. We want to get back all the skills where one of these two is true. So, we're going to have an or with an array where either the title contains the search term that we're passing into it or the description it. We also want to order them by created at in a descending order, which means that the newest ones are going to appear at the top. And we want to also apply the limit. So I'll say limit is going to be set to the limit that we pass into this query. Finally, we need to choose which properties of a skill do we actually want to return. We need just the ID, the title, the description. We also need tags created at field and the install command. So we need to get these different things from a skill in order to be able to show it. As you can see, ID to map over it, title is being shown right here. description is here. Tags are here. You get the idea. We're telling it that we only want these data pieces back from this query. And that's the power of GraphQL. So, if this syntax is looking a bit weird, you might want to take a crash course on GQL. But I think you're getting the idea. It's almost as plain English. And now after the install command, I'm going to add author as well. And this is where that join comes in that fetches the author data in the same SQL query by extracting, username, image URL, clerk ID, and the email directly from the author. And the author is just the user that created this specific skill. So that way we'll also be able to create these pieces of data. Now, we quickly want to head over to dataconnect. yaml file. This is the one and we want to point it to our new folder because if you skip this, Firebase will look for the old example folder and fail to find your new query. So simply modify it to connectors. Then we want to deploy this new query using globally installed Firebase. You can do that through MPX by simply typing Firebase deploy- only data connect like this. So when you deploy, you're actually registering your GQL files on the Firebase server. And this means that your production database refuses to run any queries that aren't already deployed. So if a hacker tries to send a custom GraphQL string to your backend, the server simply says, I don't know what that is. And that's why we need to deploy after writing any queries or mutation. And now you can see our deployment is complete. Oh, and since data connect is a relational or SQL database, the shape of your data is strict as defined right here in the schema. So, Firebase provides a way to generate an SDK that tells Typescript exactly what your database looks like. So, by running this additional command, which I'm going to run right here, Firebase data connect colon SDK colon generate and press enter. Firebase will create TypeScript files in this folder right here under source data connect generated and then you can find this index. d. ts where we define different types and it also gets the types for the full queries themselves like get skills data which we just created which tells Typescript exactly how our structure is going to look like and what this query will return get skills skill key and so on. So now let's create a Firebase config file right here within source. Then head over into lib and call it firebase. ts to connect to database connect and use it on the UI side. We'll use a singleton pattern right here to ensure that we don't have to create multiple database connections every time the app loads. So right here just say const firebase config is equal to and now we want to extract all of these environment variables by saying API key is equal to import meta. env firebase and now we can duplicate this a couple of times. One, two, three. So the first one is going to be API key. Then the second o domain which is going to be set to firebase od domain. Then the third one is going to be set to I think we had the project ID. So this is going to be set to vit firebase project ID. And then finally we're going to have an app id which is going to be vit firebase app id like this. And then we have to initialize it by saying export const firebase app is equal to. And we need to import initialize app right at the top by saying import initialize app coming from firebase slash app. So now you can simply say initialize app and pass in the firebase config. Now if the app has already been initialized then we don't want to initialize another one. So from Firebase app let's also get app and get apps plural like this. So that then you can make a check if no get apps returns anything. So that length is non-existent. In that case we want to call the initialize app. else we simply get app to fetch an already existing app. Finally, we are ready to export this data connect instance by saying get data connect coming from Firebase data connect. So, make sure to import that right at the top and then to it you can pass your Firebase app as well as the connector config coming from data config generated. So with that in mind, we are ready to define a tanstack server function that will fetch the skills from the server side or from the database. So to do that, head over to our homepage by heading over to source routes index. tsx. And right at the top we can define a new function const get skills fn is equal to create server function coming from tanstack react start and with a method set to get. Then we can attach a handler to it and that's going to be an asynchronous callback function where we actually define the function. I'm going to zoom it out so you can see a bit better. And we're going to open up a try and catch block within it. In the catch, we're going to simply console. The actual error. And then in the return, we'll simply return an empty array because we weren't able to get the skills. And in the try, we'll try to fetch the data by awaiting this get skills query that we created not that long ago. And this one will be coming from data connect generated. So call it pass your data connect instance from Firebase. And you can pass additional variables or options. In this case the search term which I'll leave empty because we want to fetch all the skills and a limit set to 10. Make sense? We're trying to fetch the skills. Finally, right here where we export the route, I'll put that below. And we're going to add some additional options by expanding this object where we have the component app. And I'll also add the loader that's going to call the get skills function which is going to populate our route or page with this data. But let's not forget to return the data from this function. So the only thing you have to do right now to be able to uh consume that data is say const skills is equal to route use loader data. Doesn't tan stack make it super simple? And now we can use that data coming from the loader or coming from our database instead of our dummy skills. So remove the dummy skills at the top and then you'll have to refer to real skills. So modify this part where you have the dummy skills and you'll see that it'll complain a bit. That's because this data is an object containing the skills. So what we have to do instead is return data. kills which is going to be the actual skills. Now it's complaining about the fact that we don't have a project ID on it specifically that we need to pass it to initialize app. If you remember, this is what we have been doing here where we had the initialize app and to it I simply pass the Firebase config which then needs to have the project ID. So project ID right here is set to v Firebase project ID. And let's verify that we actually have it within our env. It didn't find it initially. So if I head over to our env. local, local. We are looking for Vit Firebase project ID, but it's not there. We only have a Firebase project ID without the Vit prefix. And same thing for all the other keys. So, let's fix it. I'm going to go ahead and copy this V prefix and put it over here. We need it in front of the Firebase API key, Firebase app ID, Firebase project ID as well, and the O domain, which we don't even have it looks like. Yep, O domain isn't here. But thankfully, we can easily add as the O domain is simply the project ID and then we can append the. firebaseapp. com to it. So with this we should have all four V variables that we're then using within our Firebase config. I'm going to search for them. This one appears. This one appears. And this one is here as well. The rest can remain as server only such as the private key, the private key ID as well as the client email and client ID. I'll stop the application from running and rerun it one more time with mpm rundev. And you can see that now it loads. And if you scroll down, you can see only one skill, which is the performance tuner, the skill that we added manually through our SQL mutation using the Firebase data connect extension. So this means that we're successfully fetching the data from our database. But you can still see that the logo doesn't seem to be correct and there's some additional data here that we want to fix. So let's head over into the skill card and accept real data. First, I'll define a type right here at the top by using the direct Firebase generated type. So, I'll say type skill card props is equal to get skills data coming from data connect generated and specifically we want to get the skills and then number like this and then we can use this skill card props right here. So now it's going to tell you that the author email doesn't exist nor does the category. So we can remove those and instead we're going to add the author itself. This is basically a user that we joined to the skills table. And the category we can extract from the tags by saying con category is equal to tags zero or we're going to set it to general if it doesn't exist. here where you have your name under author copy we want to make it author dot username and then we're adding a new date from when we created the skill and also within the author instead of rendering this fake image we can now render a real image that has a source set to author image URL or if that doesn't exist we can use the same logo 512. png png. I'm gonna apply a tag that's gonna say well basically we have to refer to the author's name or author usernames avatar and then I'll apply a class name of avatar and finally to be able to see it we have to fix this author email as well which we have right here that's going to be author email and you can see how since the application is heavily typed we immediately know which different properties we have on the author object. This is amazing. So now if we save it, you'll notice that now we have JS mastery. That is our username. And I'm not sure why I have this dash right here. We want to get rid of that. There we go. That's better. This performance tuner skill has been created by JSMy on this date. It has a tag of AI agent. That's fine. And then if you think about it, this is the real skill that we added to our database through creating a special mutation. So let's expand this and check it out in its full glory where we have this tanax start homepage and where we're loading the skill coming from the database. Now there's one thing I want to show you. If you press F12 or open up your developer tools and then head over to settings preferences and you disable JavaScript, you can do that by scrolling down under debugger and click it right here. We're using this to simulate that everything is serverside rendered. So just disable it, reload the page, and you can see that the card still shows, which means that there's no client side rendering happening with data fetching on UI. It's a whole HTML file that you're getting back. Okay, we'll bring it back as that JavaScript also brings back our theming. There we go. And with that done, let's actually open up a PR so that we can review all of these changes that have something to do with our Firebase data connect setup. basically adding a database to our app. This is going to be a large PR and I definitely want to spend some time reviewing it and it's going to serve as a beautiful recap of everything we've built so far. So run git add dot git commit-m implement firebase data connect as a database and then run git push. This will push the latest changes to the dev branch and we won't review them right here within the editor because this is a more detailed commit. which I definitely want to review within the pull request itself. So head over to your repo and open up a new pull request from the dev branch to the main branch and immediately code rabbit will start processing the new changes in this PR to extinguish all the bugs. So let's give it some time because this was a large PR with 162 files changed. So let's see how well Code Rabbit can figure out which one of these files should it check and which ones should it not and then provide us with a valid review. And the review is in. We get this nice summary provided by Code Rabbit where we added signin and signup pages for user authentication, integrated analytics tracking for user interactions across the app, and connected the back-end data infrastructure for skills management. In this case, it also included the PRs or the code for the PRs that we checked internally through code rabbit such as the o or analytics, but it's fine. It's here as well for us to check once again. Then we added documentation for firebased services, genkit development, and AI integrations and updated configuration files to set up data models for the back end. Now, let's dive into a bit more detailed walkthrough. So this PR introduces comprehensive agent skill documentation for Genkit and Firebase. Basically what's happening here is while our data connect was getting set up, it created some agent skills that tell our AI agents to know how to use these specific tools. So these are guides for developing Genkit across Dart, Go and JavaScript and specifically how to set up and use Firebase. So this way we can ensure that whenever you're implementing a specific tool through an MCP or through just a coding agent, it knows how to do it well and up to the standard. That's the future of development. Do you need to have pushed this over to GitHub and have it reviewed? Not necessarily. Uh you can just develop it yourself and then push the code as you developed it. But if other members of your team decide to continue adding Firebase features, it doesn't hurt to have it right here. There's the cloud integration skill as well as Firebase data connect schema and operations. So these are the ones we built for defining the schema, the user and skill tables, database config connector setup and CRUD operations for users and skills. We added some routes for routing the navbar and the skills where now they're updated with post hog event capture. And yeah, this div contains a lot of files but not many of them are super important to review yet some of them are. There's some major ones right here and then some that Code Rabbit deems critical. In this case, I don't want to dive deeper into the agent skills because this was autogenerated. This is maybe something that the Google developer team should really check out. So, I would recommend that you take some time, go through these comments and apply fixes to the codebase, make a push, and then merge. I'll merge it right away. And with that, our tanstack start application is in its best state yet with full authentication that is now also connected to the data coming from the database. Now we inserted this skill into the database through a VS code extension. So naturally, one of the next steps is to implement this publish skill page that would allow signed in users to add new skills directly through the skilled app user interface. Our
Going further
Tanstack app is in its best state yet. Full o real user identity flowing into analytics and a homepage pulling live data from the database through server rendered loaders. We inserted that first skill through the VS Code extension. But the obvious next step is a submit skill page so signed in users can publish directly from the skilled UI. And that's exactly where the proc course picks up. We continue the build from right where this video ends. Submit skill page with server function mutations and zod validation. Explore page with streaming search results that load progressively as data arrives. Dynamic skill detail pages with full SSR and one-click copy. Optimistic bookmarking with tanstack query so saves feel instant. Community upvotes route masking for modal views. middleware to protect secure routes and a live deployment you can actually put on your portfolio. Oh, and alongside the build, there's also a couple of things you missed in the crash course. There's a full theory track covering type- safe search params, SSR streaming, caching strategies with stale time and preload time, serverside o middleware testing, and the patterns that separate a tutorial demo from a real production app. The link to the pro course is below. Check it out and continue the build.