Nuxt 3 & Nitro
Nuxt.JS v3 is stable, and it introduces a ton of features including Typescript support, Vuejs 3, Vite.JS, and the new Nitro server engine. Framework architect Daniel Roe will teach us all about it.
Links & Resources
Click to expand the full transcript
Captions provided by White Coat Captioning (https://whitecoatcaptioning.com/). Communication Access Realtime Translation (CART) is provided in order to facilitate communication accessibility and may not be a totally verbatim record of the proceedings.
JASON: Hello, everyone and welcome to another episode of "Learn With Jason." Today on the show we have Daniel Roe. Daniel, thank you so much for taking time to join us.
DANIEL: It is a pleasure to be here, Jason. Thank you for having me.
JASON: Of course, of course. I am super pumped because I feel like this is an episode I have been asked about a lot. We get a lot of questions about Nuxt. I think there was a huge amount of anticipation for Nuxt 3. Everyone is so excited it is live and I can't wait to learn more about it. For folks not familiar with your work, want to give us a little background?
DANIEL: Sure. I am on the team of Nuxt which I guess probably goes without saying. Before that, I had a small software as a service startup that was providing resources for working parents. And before that, I was in agency world. Again, a small digital agency working on -- focusing actually on communication at first and figuring out what was most important to communicate and get across. And yeah. I could go back further and further and further. But I guess I could say I am self-taught. I have always loved coding but this most recent foray into open source is short and you can look on my GitHub contribution and see it started a couple years ago. So yeah, it is a pleasure to be doing what I am doing at the moment. You might not have known that I would do it if you were to peek at my life 5-10 years ago.
JASON: No, that's fascinating. The journey is always really fun because it is never the same. Nobody I have spoken to in open source has exactly the same path into it. I am curious, because one of the main things I hear when you talk to a lot of open source contributors is that it is a challenging place to be. Being on open source contributor is, you know, it is kind of a firehose. Like a lot of people use the software especially something successful like Nuxt where you get a ton of issues, feature requests and always more than one person or even a small team really has the bandwidth to handle. How has that experience been for you? What's your experience of being, you know, a framework architect on Nuxt?
DANIEL: So I can just say, by the way, your backdrop is memorizing. The colors changing. If I at any point slip into a trance or something like that that will be what it is.
DANIEL: I know the -- it helps that the Nuxt community is so nice. I know what you mean about the firehose concept. So there are lots of avenues for people to get in contact which I actually appreciate. I am reachable, for example, on Discord and Twitter and there is audible GitHub issues and discussions, not just for the Nuxt 3 repo, but the other ones and you can pick all of the module repositories and there is lots and lots of opportunity for people to get in contact and equally opportunity for important issues to come through. So I think the Nuxt community, though, is really appreciative. They say thank you. There is a lot of love. I wouldn't be surprised if this is true for other open source maintainers but one of the key thing for me in open source is this is about giving out to people so just as I also have received from other people. It feels like a tremendously rewarding experience. I am helping people. I sometimes get the opportunity to help in a more sort of one-to-one basis and sometimes it is an on issue level or a feature level and that is so satisfying, honestly. Having that people saying thank you and appreciating what is being done makes all of the difference. For that, it really helps having a team who can give you a bit of support when you encounter that. I think probably because open source is people focused at least for me, it is an opportunity for great reward and also hurt as well but that's not really primarily in any way what I experienced.
JASON: Sure, sure. It is a good reminder to all of us that, you know, for a lot of folks the only feedback they hear is when somebody is frustrated so to take the time like it sounds like the Vue community does to say thank you, the shout-out the maintainers, to share the good times that things are working, that you are happy and you love the software, that is the sort of thing that helps make sure that the folks building this stuff stay, right? If they hear more good than bad it is easier to stick around and keep doing it so that's wonderful. That makes me happy to hear that the Vue community has done so well. I was talking about this the other day, we did a state of JS survey recap, and one of the things I was thinking about was there is this hype cycle that happens with a lot of languages where they kind of explode out of nowhere into prominence and then everybody uses it and it becomes the de facto tool and then the thing people have to use and then they get frustrated and are grumpy like J pack did it, React starting to do, and Vue skipped that and went straight to a dedicated community. It has slow and steady grown, huge community base, huge active community, a wonderful ecosystem, and it feels to me like the folks that are there is because it is what where they want to be and what they want to work with and you don't have the people were grabbed kicking and screaming into the Vue community against their will like when you see every bedraggled React engineer you will see this. It doesn't matter how much Eric is doing to make redux wonderful and it is really is but you can tell a lot of people feel like Redux was thrust upon them and they have to deal with it. If only and woe is me. I haven't seen that same, oh God, I have to use Vue. Is that true on the inside of the community or just my rose colored glasses looking in?
DANIEL: I think that's a fair thing to say. I think the biggest moment of sort of dissatisfaction in the community was really when the composition API was introduced. I think it must have been a couple of years ago now. And the -- I think a lot of it was worry and concern about what was going to happen. Now, the majority of new developers not only use the composition API but use the script setup syntax as well that goes with it. That was a moment where there was a sense of saltiness and people were unhappy but, no, I think you are recognize. The Vue community is a smaller community than the React community. That helps in terms of keeping that closeness. It is also a very international community which is a huge strength.
DANIEL: And certainly probably means we have to work quite hard at making sure we are communicating well. That is true whenever you have any kind of distributed team and I think you do see in Vue this feeling of we are a community and we look out for each other. Vue has had fewer options, like fewer anointed options, like for the Vue router was the way, Vue X was the way and I think that probably made a little bit of a difference as well in terms of thinking about how on ecosystem feels. So rather than picking lots of different options, you were a little bit more guided in Vue and in terms of what you were building with. And also what the other people you were talking to were building with. Again, less opportunity for holy wars of one tool versus another tool. Things are a little more open at the moment. Obviously with Vue 3, there was then a rethinking of store. Not just to pick on your Redux example. Vuex had its own plan, plans for another release and another store came along which was then adopted by the community and adopted by the U-core team as well and that's the official store solution now. There are others like Harlem too. So the ecosystem is a little bit more diverse in Vue 3 and we see that even in the Nuxt ecosystem. There are often several different solutions for any given task so that is interesting but it is not getting rid of the warmth and the closeness of the community. I might be wrong about the number of different tools affecting the cause of something different there.
JASON: Sure. It does feel like that has been a point of contention. The lack of guidance. If you look at the React community there wasn't an official recommendation on how to do things and that led to people who were not part of the React core team becoming the official spokesman of how things should or shouldn't be done and that often led to tension, like we have seen that, where a recommendation gets made and there is a Twitter cycle of everyone saying not like that, it needs to be like this instead, and we are not sure who is correct because nobody is correct because it is just a community self-governing and it has led to pretty heavy fracturing. You have folks who are also redux, folks who are never redux, folks who are always whatever tool they love. I am going to take a quick aside . I know you are seeing a green flicker and I ordered a cable but I can't do anything about it.
DANIEL: You mean it isn't a CGI thing? This isn't a computer generated Jason.
JASON: The real Jason has been on a beach somewhere for years and I am an AI. No but no, to maybe switch from all of love in the Vue community, I am going to ask what's maybe a controversial question but it feels like some of the grumbling I heard is Nuxt 3 took a long time to get stable and that potentially hurt the ability to grow and gain adoption because Vue 3 was out for a long time first. People wanted to use Vue 3 but couldn't because it wasn't working in Nuxt 3. Do you feel that that's true? Or is that sort of, again, like me from the outside hearing the loudest minority instead of hearing what was going on in the community?
DANIEL: No, I think you are absolutely right Nuxt 3 was a long time coming. I can give you a reason -- we could sort of explore a little bit about why that might have been. It was, I am sure it was really frustrating for people in the community. It was very frustrating for me as well on the team. And that's not just me. The whole team would have been frustrated by that. That wasn't great. Probably the experience of waiting so long for that I can't imagine that was a pleasant experience. We really very much, probably, pretty much top priority never want that to happen again. The upgrade to Nuxt 4 when that happens is not going to be the same kind of thing. The delay is not going to be nearly as long ever.
DANIEL: We really are taking lessons on board from that. Having said that, I was really pleased in the state of the JS and seeing Nuxt users are positive about the framework and retention even given the long delay to Nuxt 3 release. That was gratifying but no, I bet it was frustrating.
JASON: We have to assume, you know, this was worth the wait, right? So a lot of good stuff showed up in the Nuxt 3 stable release and a few of them, I understand, and a few of them I am going have to have you a lot of questions ability. I understand Nuxt 3 is now TypeScript by default or TypeScript capable?
JASON: Great. Great. The other one I saw is you are now using Vite.
DANIEL: Interesting. You can also use Webpack as well. Nuxt is bundle agnostic. As long as you have a bundle function you can have any bundler that exports a bundler function. We have two first party, a Webpack and a Vite builder and you can use either one. Nuxt ships with a Vitebuilder. You have to install the Webpack one differently if you want it. I think people are most excited about Vite. It is pretty amazing. I think what's what drove adoption first. I am most excited about the ecosystem for Vite. Just like Nuxt has their own ecosystem, Vite's ecosystem I think has driven option adoption of it as a framework so it is a delight to extend as a library author. And then the idea you can write something, I wrote a couple libraries that do this, that basically can run in any framework that uses Vite. Library I am talking about is Fontane. It uses font metrics to provide fallback fonts when web fonts are loading so it uses metrics to adjust the system fonts so there isn't as big of a layout shift as there would be. Random thing but the fact is the Vite plugin that module exports can be used -- to use it in Laravel is straightforward, to adopt it in Solid in totally possible. That means, I think that's great. And that's the kind of thing about Vite really enables quite well.
JASON: That's really, really cool. And then let's see, what else is new? So the big one I think is one that I don't know much about. So I am really interested to hear. There is something called nitro happening in Nuxt. Can you talk about what Nitro is?
JASON: Oh, fascinating. I love that you said kicking and screaming so I imagine this one of those ah, yes, we will just move this code over here. [Laughter]
DANIEL: We built it to be extractable from the beginning so the first instance of Nitro was as a module so most things could be built into Nuxt by means of modules. It is really sensible that way. But we first built it as a module and then we integrated it much more closely and the danger with that, even if you keep it as a separate package and mono repo which is what we did, the solution to problems or the API ends up slightly merging. So, for example, maybe we need to transpile certain packages as part of Nuxt and was just easy to add that to the Nitro config. You can have a sort of bleed over where it is just expecting to be used by Nuxt so pulling it out was slightly more complex and if I recall correctly I think we had a separate repository and also still one within Nuxt. So we sort of had two parallel implementations for a little while until we fully pulled it out but, you know, we did it. It is independent. I think that does help us; you know? Nitro Pack needs to be its own thing. It isn't just driven by Nuxt and needs to be usable by the rest of the ecosystem too. There are people using it who aren't using Nuxt.
JASON: Gotcha. At this point, I want to be mindful of the fact I am not super strong with Vue and I don't want that to prevent us from getting things done so I want to give more time to work on coding so from here I am going to bounce over into the pair programming view and start off by saying this episode, like every episode, is live captioned. We have it closed caption button right on the Twitch player or if you look theme page of the site we have the whole transcript here. That is being done by Maggie by White Coat Captioning and thank you for being here and that is made possible by our sponsors Netlify, NX, New Relic and brand new Pluralsight. Thank you so much for kicking in making this show more accessible. We are talking to Daniel and go follow Daniel on Twitter for all your Nuxt Texas -- oh, you are building elk zone which is this really good -- I love this. It is so nice to use. If you go into elk zone, it is a Twitter-like Mastodon client it is wonderful. This is very, very cool. If you are using Mastodon go check it out.
DANIEL: And check out Mastodon as well.
JASON: Oh, yeah. Check out Mastodon. What's the right place to go? Is it dot-social?
DANIEL: JoinMastodon.org is the website.
JASON: Go check out Mastodon. It is a surprisingly cozy place. I have really enjoyed the conversations I have had on there and the tone is like builders helping builders, people being supportive and a little less of the random drama you see on Twitter. You can ask questions with a little less likelihood of someone floating in and telling you why it is wrong you exist in the first place. We are talking about Nuxt. Did I drop a Nuxt link? I don't think I did. Here we go. This is Nuxt. I am at the end of my knowledge. What should I do first if I want to build a Nuxt project?
DANIEL: So this is a great -- this is the central place to start but I can give you an even quicker way to get going and that's to go to Nuxt.new.
JASON: This one here?
DANIEL: That one will start you a new Nuxt project if you run that in your terminal. You can replace an app with the name of your app.
JASON: Nuxt 3 Nitro.
DANIEL: That is a node warning.
JASON: We are good with that. I am going to go into Nuxt 3 Nitro and we have the basics of a project here. Let's see. Is it going to yell at me for not having a -- no, we are OK. Nope, it is. I am going to close this real quick and get emit and open it again. Now it won't all be grayed out on us. Starting in the readme. We have got the ling to the docs, basic setup, always very nice. How to run a dev server and thew -- how to build for production. Wonderful. I am coming out here. I use NPM still. What's the right way to familiarize yourself if you are not super familiar with Nuxt to get your head around where things live, where you should write your code, you know, how you would extend it if you want to do more.
DANIEL: So the best way is to dive into the docs. I can tell you just what you have in that folder but if you are wanting to know more generally how to get going, if you go become to Nuxt.com and click docs. Sorry. Then you click -- let's see. I think API. You will see on the left hand side directly structure. Obviously you can read through the rest of the docs. But the directory structure is a helpful piece of the docs and that's why I am mentioning it here specifically. You can see all the special folders you might have as part of a Nuxt project or the special files. It will tell you what they do and how to make best use of them. That is a helpful place.
JASON: I have to say. I have never seen this in docs before. I love this. This is such a clever way to approach it because when I first open up a project, I am always looking at the directories and sometimes it is hard to know which ones are magic, which ones are the template author organizing files, and basically how does everything fit in here. To be able to say my project has a layouts folder what is this for and you just have given me the names of the directories so I can go look and then I assume that based on this, if I see a different directory name I can safely assume that was a preference of the person building the template and not something that I have to worry about like I can change things, move it, and not have to worry.
DANIEL: And most of these can be customized too if you really want a custom. But, yes, this is what Nuxt ships with and what most Nuxt projects would have. I realize this isn't for everyone but I personally prefer the minimal starting project because you can quickly --
JASON: It looks like that's what we have got here.
DANIEL: It is very minimal. You can get ahold of what is there without necessarily thinking what do all of these files have do and have to go through everything. This minimal project comes with package JSON which only has one dev dependency which is Nuxt and whether it is dev or production this won't be used in production. It is only for development. App.Vue is the Vue entry point. The client-side piece of your app. Everything else comes from here. If you need to do something top-level in your Vue application, you do it in this file. That is probably quite similar to most Vue applications. They will have an app.view file. It tends to be per convention. If you were to start your Nuxt server now then you will see the contents of this file in the browser. If you want to, yeah, go ahead and install the recommended extensions. Villi powers all kinds of cool things within Vue files and we definitely recommend that. I don't know if you need to reload or not.
JASON: Let me close and reopen to see if that caused -- looks like it turned off everything.
DANIEL: Let's try running a dev server.
JASON: Something is happening.
DANIEL: It basically runs a post-instance or command to create type devs so the editor is aware of what welcome is and props it accepts. Do you have Vito installed from a previous time when you used Vue? This is telling you. I would recommend you disable Vita. They are two different plugins that do different things. -- that do the same thing so best to have only one enabled.
JASON: That's OK. I wasn't using it and I didn't know I had it. Here we go. I have got this here. It does look like it is doing some stuff. We have our types. And it shows us -- go ahead.
DANIEL: That particular type isn't going to tell you a great deal. That's a type of component in Vue. If you were to type after it, if you just space and maybe a colon and it has stuff like title inversion. This component isn't really for people to use but it does have some props. It is quite nice. Nuxt welcome is auto imported into your project like most components you will use in a Nuxt project. You don't have to explicitly write the import. Your editor is aware it is available and when you do use it in your Nuxt pages or components, Nuxt will generate an import statement there for you. So it will be -- these things are not globally registered but they are still imported directly but Nuxt does it based on usage.
JASON: Got it. That makes sense. And I am assuming since this isn't for general consumption, I can sort of delete this. That feels like there is a lot of -- go ahead.
DANIEL: That's beautiful though. You should keep it.
JASON: I am going to start it up. NPM run dev. It has started my server so I am going to grab this and we will head out here.
DANIEL: There you go. You have seen it. Look at this.
JASON: This is wonderful. This is like the built-in welcome. That part is magical. I assume this is coming from the Nuxt package. If I delete this and change it to hello, chat and come back out we have plain old page. If I look under the hood, it looks like we have our div and not a lot happening that I didn't explicitly put there which I very much appreciate in a framework. OK. What should I do next here?
DANIEL: What do you want to build?
JASON: There is a great question from eminox about loading some data. I have this API we can use and go API v2 and grab episodes. This will give us every episode or we can switch it out for like the schedule, whoops, and get a shorter list. We will be able to use this to build out a listing of things and answer the question of what's the baste -- best way to load data in a page.
DANIEL: OK. Let's display that in this app.Vue. One thing you are probably going want to do as you build a bigger application is have some routing. You might want to have different pages, URLs going to different things. At the moment you just have one app.view which is rendering every request to your application which is fine and maybe what you want. Nuxt ships with a minimal universal router that will allow you to do things like access the location through use route and you could conditionally display different things based on changes to that route object even just in an app.view file but most are going to want to enable routing. We can come to that in a moment and first look at how we will handle the API call here. If you type at the bottom, if you open a script tag and instead of making it script, add the second word setup after script. Script setup. You can add langts if you want and that might enable more type support and shouldn't change what you need to type. Lang="ts". Nuxt ships with composables that let you do asynchronous data fetching on the server and still have access to that data on the client without needing to reload it. To start with, the way you would fetch from an URL in Nuxt is use a helper called $fetch. You can say const data and await fetch and that will fetch it on server and client. I will show you how to use a Nuxt composable to prevent that. $fetch which you use throughout the app in handler functions and everywhere you call a user interaction and that will automatically parses JSON responses and runs to data directory. You don't have to call the JSON method and then await that for example. So you could just display that in your template and maybe you could try that if you wanted.
JASON: Sure. I will just do a standard pre-tag here and I would want to JSON stringify and then can I just use data straight up? Or do I have to export it?
DANIEL: You don't have to stringify it if you don't want to. I think it will do it automatically if you just put data in there and one more pair of curly braces around data. This is the weird thing for Vue if you are coming from JSX world. Two curly braces. It is not an object. It is just a template.
JASON: Got it. Is this mustache is the syntax? Is that right?
DANIEL: Yeah, I think that is what it looks like. Exactly. I think if you were to just save that that should work. Everything in the script that is used in the template is exposed automatically to it. And everything in script setup that's not used in the template is not going to be exposed. So there is some magic done by the Vue compiler there to make sure that you -- that things work. Script setup is a shortcut for export default component object and then inside that object having a method called setup and inside that setup method doing whatever you are doing in the script setup log and then returning from that function the things that will be needed into the template. But that was a lot of broiler plate and the strip setup block is a brilliant improvement on that developer experience.
JASON: Yeah. I mean this is -- it definitely is one of those things that will take a little bit of you know, you have to build a mental model around this because there is a little magic like this showing up here is great. I would have expected you would have to export an object of what's used but you are abstracting that away. Anything that's get used is being included and anything that doesn't get used I assume gets discarded as part of the bundling process.
DANIEL: So I think -- it depends on whether or not it is thinking it is something with side effects probably.
DANIEL: If you to maybe have a lint rule that tells you if you have an unused variable, if you comment out the predata block and you can also do it this way. If you put that there, your editor and your linter or checker will know it is not being used. It is not like you are having to track what's in the template. It behaves very much as though the template is part of your code.
DANIEL: Let's refactor that statement. If you have a look in the browser's DevTools and reload you should see an HXR request happening on the client side and it should be hitting your API.
JASON: I haven't set-up cores on the schedule which I should have so that's good thing to know that I have done.
DANIEL: It will be fine actually in this case because we can render it on the server and OK. If we go back into the editor and let's get rid of that request entirely on the server side and replace $fetch and you can copy the entire line because it will mostly stay the same and replace it with useFetch. And you can put curly braces around data because the object that uses the one -- sorry. The one in the script setup. Object use fetch returns has several things -- it has data, an error thing which will have an error if there has been one, it has a pending property which will tell you whether the request is running. It has more as well. You can re-execute the request. So this should work in exactly the same way. If you go back to the browser and refresh that it should also run and there should be no client side data fetch. You should be able to see -- if you look at the HTML, that is because this data object in this case, it is there twice because we have actually outputted it directly in the template but it is also in this script tag at the bottom. We have this window.Nuxt object which has a payload and that payload will include every that needs to be parsed from the server render to the client side and in that case, you see it there.
JASON: We can probably go ahead and clean this up just a little bit to show the difference. If I want to, instead of just dumping the data, we want to setup a loop which, let's see if I remember, it's V-for.
DANIEL: That's absolutely right. What do we want it to be?
JASON: Like an article, right?
DANIEL: Yeah. So you will do whatever event or --
JASON: Data as episode.
DANIEL: The other way around. Episode in data.
JASON: Episode in data. Close my article back here. I can do like an h2 and we would want the episode title?
DANIEL: Yeah, beautiful.
JASON: And then I want -- I will do episode.description. Is that what it is? Description. And then we will do a link. This is one that I need to remember. I need to set the attribute, which means I need to bind it? It is it like this?
DANIEL: Perfect. Exactly.
JASON: Then I put in episode.uri.
DANIEL: Beautiful. You were misleading me. You are like oh, my Vue is terrible but clearly --
JASON: It has been a minute. This is great. Good. We got it moving. It is moving. And then right now if we click it would go through to the episode but I want to change that and I want this to be instead let's go to a slug. Now I am going to need help because I want to form a string here when I want to do some routing. Let's create slash episode and slug and look it up from the API?
JASON: OK. Got it.
DANIEL: And that should -- it is unhappy because the data coming back from use fetch isn't typed. We don't know the type of response. We can make it happy in a couple ways. I think where you can remove lang ts and it will probably no longer complain and you can also type it.
JASON: What if we do a cheap -- wait. Hold on. I got yelled at for this. Always interface or -- we can do title as a string, the description of a string and we are using a slug so that's also a string, and I need to stick this in here somewhere and --
DANIEL: I totally love Zod if you haven't used it. You probably have?
JASON: I actually have not. I need someone to come on and teach me.
DANIEL: Do you want to use it? If you install npm zod. Nice. Restart the server and import Z from zod and that's curly braces and named export. Then what you say is const episode equals --
JASON: And you do it capitalized?
DANIEL: Whatever you like. And z.object and pass it in an object, say title is z.string, and -- yeah, exactly. There are two ways. Before we go much further with Zod, how you would type the fetch, is you would add a generic after use fetch. If you had added two angle brackets like that and made it --
JASON: Like that?
DANIEL: Perfect. That would type the data and now we don't have a type called episode any more. In this particular case, we want do two things. One, I think, we only care about those three properties so we really shouldn't be passing the entire API response to the client. If you only want to pass those ones, for this, there is actually a native Nuxt way of doing this with just, you can use a property called pick and say what strings you want and in this case we can kill two birds with stones and narrow down the API response and type it at the same time. As a second argument to use fetch, pass an option object and we want a key called transform and it will take and return data. You can take data and say episode. -- I think we are going to want to say, what is it? Pass? -- parse. Yeah, parse. And just parse the data into the it. I believe this will throw an error if there is any invalid data. It is up to you. But yeah. This should work.
JASON: Unhappy and it is unhappy because --
DANIEL: Why is it unhappy? Is it unhappy? Just try refreshing the page. It is unhappy. Oh, it is unhappy because it is an array and we are parsing it as a single object. What we actually want to do is we can do z.array and then stick -- yeah, we can do it up there as well. And just pas an array Z object and we can parse it as episodes.
JASON: Now it is unhappy but I think it is -- it was unhappy because of the trying to do the client-side refresh which is fine but the upshot of this is it theoretically should have like hugely simplified our object too. We are sending less data now as well.
DANIEL: Less data is great.
JASON: Title, description and slug and it goes to the next object. This is great. It means we are not sending unused data which is going to minimize the payload and make sure we are not -- there is that whole controversy about the social security and government website ask you could you could be hacked and someone just overfetched data and didn't filter it out.
DANIEL: That was a really awful thing.
JASON: It was a good reminder we do need to educate really well on what happens when you fetch data in any kind of app builder and making sure people understand what becomes public. It is such an easy thing to do. If you had not brought it up, I totally would have fetched all the data about the episode despite using these three properties.
DANIEL: What really annoyed me was the way the State responded to the reporter who disclosed it even though they had privately contacted the government first. They treated them as a hacker because in the HTML was actually the data. Anyway --
JASON: I know the Vue source is hacking and that was one of my favorite 5 minutes on Twitter was that story cycle.
DANIEL: Yup. OK.
JASON: Now if I click this up here, I am going to slash episode and slash Nuxt 3 in Nitro but we haven't implemented routing so this is just rerouting back to the main page and showing us the main thing we were seeing before which makes sense. You said that's the default behavior, app.Vue andles every request, and we haven't given it any way of interpreting the URL so it gives the same data for every page which depending on your outlook I kind of like this as opposed to in every frameworks where you haven't implemented routing yet it fails and full crashes and you have to restart your dev server. This, showing you clearly that nothing is happening yet, but not crashing your app is kind of nice.
DANIEL: Yeah. Definitely. OK. OK. If we head back to the app, we need to implement routing. If we create a -- but just, hover over that beautiful episode.title up in the template and you get the type; right? Now you have type.
DANIEL: I know. It is beautiful. So the thing we are going to need to do is create a folder called pages and this is going to implement routing for us. When Nuxt determines that there is a folder and that there is something in it we will probably need an index file. Index.Vue can be list episodes and then the -- it is complaining because this is not a valid Vue file yet. We can properly copy the contents into the that file.
JASON: Taking the whole thing straight up.
DANIEL: We can go back to app.Vue and delete the script setup. We don't need that now. Instead we are going to want to insert a special Nuxt component called Nuxt page. It can be self-closing. Perfect. If you hard reload that in the browser that then should have clients running and if you click that you will get a 404.
JASON: This is the built-in lightweight router?
DANIEL: We had the lightweight router available already but this is Vue router now and the full thing.
JASON: I understand. OK. If we go to pages index, sorry. If we create a page called episodes/ -- a folder called episodes and in there we will create a file called square brackets episode or slug or whatever you want dot view and another square bracket at the end. There should be a context menu option to convert --
JASON: That would be great idea. I can rename this to slug.Vue. In here, if I do a template and to do load episode details -- not yet.
DANIEL: It might take a second.
JASON: Do I need to stop and restart when we have a dynamic?
DANIEL: You shouldn't need to. If you reload the home page again and try it once more.
JASON: OK. Let's go home page. All right. I am going to hard refresh. I am going click through. Here we go. We are back. Because I have the core stuff we aren't getting the client side loading and that is to do with the API not Nuxt.
DANIEL: Yes, but we can also solve it if you want.
JASON: Let's do it.
DANIEL: OK. So we can actually create a Nitro route within your project. Nitro does support a proxy so we can just configure the proxy but it might be more interesting to create the route just to show how it works. If we create a folder called server and in that folder we create another folder called API and in that folder we create a file, sorry, called whatever you want the end point to be named. Episodes.get.ts you don't have to have that method but if you do it will restrict it and throw an error to anything else. Export default define event handler and that should autocomplete. And then you just can pass it a function. If we go to index.Vue file and take out everything from import Z from Zod and paste it into the schedule.get. And move the input to the top and the episode can probably be removed from the handler itself. Change the format slightly here. Actually, we could have gone back to that. We can say const data await $fetch. Now we will have to do that manually. Or however you prefer. Obviously in real identify we would handle the error of having invalid data. Or use safe pass or something. But in this case, yeah. That should be fine for now.
JASON: Was it parse safe was --
DANIEL: Safe parse maybe?
JASON: Yes, that is what it is.
DANIEL: If you go back to index, we can now remove all the stuff from Zod. And then at the end we can do const data in curly braces again. I wish there was a way of saying that. You can read code but I wish there was a click you could do to communicate curly braces are starting. [Clicks tongue]
DANIEL: Await use-fetch and we will pass it the API route.
JASON: Look that autocomplete. That's sick.
DANIEL: Let's see. What's going wrong there because that should be typed. Oh, it is saying --
JASON: Because we used softparse it is giving an if. Does that mean I need to do one of these?
DANIEL: I think, actually, we should probably in this case just revert to parse.
JASON: Let's not spend the whole episode fighting with text and we will learn how Nuxt works instead.
DANIEL: We are getting type safety through the server to client boundary can I just say? We are not having to worry about the type.
JASON: That's really nice.
DANIEL: That should actually work if we go to one of those pages and come back from it. The cause is no longer an issue because we are hitting our own BFF.
JASON: We would probably want to do the same to add episode data. Do I just do this ts? Or do I need a variable for the slug?
DANIEL: We probably do need the slug. If we said episode square brackets slug end square bracket and get.ds that should do it.
JASON: I will copy this because it will almost be identical. It just won't be an array.
DANIEL: In a really project we will extract the episode thing and have it in a shared folder.
DANIEL: But in this case we will want to get access to that slug.
JASON: And then we would need the slug. So I am going to switch this over to these and then we want the slug in here which we need to get access to from where?
DANIEL: If you type const slug equals --
JASON: You know, it works. [Laughter]
DANIEL: Get router perams and actually we could probably do that. This is expecting something called an event which is where all the information about the current request lives. That is actually the first argument to this function. If you just receive it in this case and then pass it through there we now have access to that slug. You know, audible you might wapiti to do some kind of -- want -- validation because presumably someone could do all kinds of things parsing. You wouldn't want an unsafe random slug to be added to an API call I guess but any way, passing over that. But parsing over that should work and this should give you an episode.
JASON: We can do a handful of things here but the good news is then if we go into here and grab data and went to stick this down here but instead we are going to do -- will you just autocomplete for me? We will do episode slug here and do I want to do it like this and there is an option object or template it out?
DANIEL: Just template it out and that should be good.
JASON: We are going to switch this out for a template tag. We will do slug here and where does one get the slug? Same deal?
DANIEL: Const slug except not get router perams. It is a little different because we are into Vue side. Equals use router and we -- use route.perams. That should give us -- I am curious. Yeah. OK. If you have a look that is actually typed as well. Oh, beautiful. Beautiful data. We are actually able to do that esc even though there is a template literal with dynamic information there because we have in TypeScript implemented our own router which within TypeScript actually matches the template literal you are passing there with the actual array of a possible API routes that are coming.
JASON: That's so, so pleasant. That is wonderful. What you've got here is I think what everybody so excited about with what Tanner Linsley just announced for TanStack query which gives this function to React I believe. Really cool stuff. Very, very exciting to see how well this works and how nice it is. This is lovely being able to know if I do a good job of typing my data here that we get more information and so one thing I am going to do is I am going to get us a little bit more information and I am going to do it the long way because that way we don't have to rewrite a bunch of stuff and I am going to just pull up the episode and this will give us detail. I want like let's give us the -- I want the image. Do I have to build it? So we are going to build it. I will do that by getting the URI.
DANIEL: I see a guest and host image?
JASON: That's our head shots but I also generate like a poster. This we are going do -- rather than try to figure out how I would transform data in this parse I am going to do over here. So what we will do is let's build out, and this will also be good because we will introduce an image. I will put an image in place and I will do it the way I know how. So this will be source equals and then I want to my template and it is going to be the data.image or uri and then we will do poster.JPEG. That's how I generate my images. Alt will be the data.title. Down here we will change this one out were the episode title. Let's put the description down here. Data.description. It not happy about something I am doing. I have to finish my template. What are you unhappy about? The data is here.
DANIEL: It is saying it might be null which --
JASON: I can fix that.
DANIEL: You can wrap the whole thing or -- yeah, you can do that.
JASON: Easy enough. Get these in here and get you to stop yelling at me. Are you happy? You are happy. I am going to close this one and...
DANIEL: Oh, I know this is probably do need to restart the dev server but while we do that, if we install another Nuxt module, Nuxt/image-edge.
DANIEL: Yes, the edge thing is pre-released thing.
JASON: Oh, image@edge?
DANIEL: Should be just the dash. Why is that not installing?
JASON: That was my fault. I stopped the install and tried to change the thing. Now we have image-edge is installed.
DANIEL: Yeah. OK. Anyway, if you refresh the page it should work now. Sorry about that.
JASON: Did I restart the server? I did. Let's go in here. There is our giant image but yeah. This is the generated images you can just attach/poster.jpeg to any episode URL to get an image. I should just put that into the API response.
DANIEL: That's very cool.
JASON: We are loading. Like, it is working.
DANIEL: It -- the moment you start handling image I would recommend using the Nuxt image module we just installed. This will show how to get started with Nuxt modules also which are little packages that can -- image.Nuxt.js.org. This basically will let you do all kinds of things like add Cloudinary support for transforming that image that you have. It even ships with its own transformer called IPX which can be self-hosted or run at a generated time. For example that is quite a big image and if you don't use it all on the page it would be nice have to different variants. So go to the Nuxt config file first which we haven't touched but we can add a property called module to it. That's an array of strings. Just add Nuxt image-edge. Just the package name. That will actually automatically restart Nuxt for us. Whenever a new module is installed Nuxt will add the text to the environment as well. If you were to go into your Nuxt config again now you will find that there is another property in your Nuxt config that you can set called image and that should be typed based on this image module. In this case, I don't think we will need to add any config straight away.
JASON: Fascinating. Something magical just happened and I feel like you are like that will just happen. Because we installed the Nuxt image-edge and added to the modules you extended the types to include the options for the module?
DANIEL: Yeah. And if you want to see how it happens you can open the dot-Nuxt folder. And all the magic is in here. If you go into the types folder, most of this is types. We have lots of virtual files but they are kept in memory. You look at the schema.d.ts file. You will see what we are done is added for every module that's used we added its config key and its types in the Nuxt config via orientation. That's how you get access in your Nuxt config so you can have type safe module offers based on the choices.
JASON: I am not going to lie. This blows by my [Beep] Mind. When you explain it is thoughtful but this is the attention that makes something delightful to use. It is just so pleasant when you just open something and you start building and the thing just happens. That's so nice. It is so nice. OK.
DANIEL: It is nice.
JASON: We aren't going to need it but I want to poke in here and see what happens. We have there ability to do alias, CloudFlare, cloudinary, contentful, look at all these providers.
DANIEL: Those are the providers that have their own options. You can use lots of providers at the same time. You could use cloudinary also as well as fastly and image kit and whatever. You might want therefore to set lots of different options objects. There are other things that you can set too in terms of -- one of the things that will happen is that we have some default screen sizes that images are optimized for. You can set those. You can configure what those might be. But the defaults are probably good enough.
JASON: We will skip on configuration and jump right to using it then.
DANIEL: The way Nuxt image makes things available is that it registers some components for you which you can use. If you replace image with Nuxt IMG, exactly, that should just work. It has its own propers and so there are additional propers but it is an image tab. There was -- it should be a drop in replacement. Just like Nuxt link renders an A-tag a Nuxt img will render the image tag. It is also adding functionality that's unique to Nuxt on top of that. There is another one you can use also called Nuxt picture which I often turn to even more than Nuxt image because it will generate source sets for me which --
JASON: And image won't generate source sets?
DANIEL: I think it does.
JASON: Let's check.
DANIEL: Let's have a look at the config there. In that case let's see. Ah, because it is a remote image. So we normal are expecting images to be local and it is going to do various things for us there but I think we are -- that's just your site, right? There is nothing in front of it?
JASON: Ultimately it is coming from Cloudinary but this is served by my site.
DANIEL: If we take your domain and go into the Nuxt config I think we should be able nick this work. It has been a while since I configured this so I might need to check the docs. You are there.
JASON: Autocomplete to the rescue.
DANIEL: I think honestly that is one of the best things about TypeScript. The idea that you don't need to switch back and forth from one thing to another thing like in terms of context between docs and code.
JASON: For real. My like nuclear take on TypeScript is I don't care about it except for autocomplete.
DANIEL: That is totally legitimate. Apart from that we haven't actually written any TypeScript in the project so far.
JASON: We have only benefited from it.
DANIEL: And I think as much as possible leave TypeScript to library auth, you might have to do a new project, but if it could be left to library authors and that means you can just benefit from it, that's great.
JASON: The only time I write types is if I am exporting the thing to like someone will be importing an object or a function or whatever from my code then I will type it but if it is like an internally used function that will not escape that particular file or whatever I don't bother. I just type everything as any until I am actually going to let someone else use the code and then I worry because I want them to have autocomplete and warnings and all that good stuff.
DANIEL: The concept for me about TypeScript is it is about source of truth and source of truth flows. The moment we have validated the input in our server route we can then have the type flow from there and we shouldn't need do anything else at this point. If you have done const A equals a string and TypeScript knows it is a string and it can flow through the code and you shouldn't need to annotate it but enough of that. In this case, probably best practice is we would want to set some sizes for this image.
JASON: OK. Let's do it. I am going to go in here to my image and I am going to --
DANIEL: Oh, I was thinking height and width but he can parse sizes too.
JASON: Oh, height and width. Let me tell you what I would usually do and you tell me if this is something I have to accommodate for in Nuxt or if this is something that will kind of get automatically handled. What I would typically do at this point is I would start putting together some style, which I remember correctly, I just put a style tag together and then I would say image and max width of I don't know 500 pixels or something, and that would control the width of my image. This didn't change the size so my expectation would be that if I wanted this to have a source set I would either need to provide the source set myself or I would have like a, you know, an add max width and let the image component auto determine what the right break points were for single pixel density, high density, Etc.
DANIEL: There are a couple of things that you can do. OK. First thing, your style block is great but if you add the attribute scoped to it then it won't apply this to every image in your website but just to this page.
JASON: I do love that.
DANIEL: You are totally on board. I forgot. The second thing is just let me show you what Nuxt picture does out of the box without configuration and we will explore how to make it happen with Nuxt image. Just drop a Nuxt picture, save it, and what you should see, well this isn't targeting it because it isn't an image but it is a picture. You should see massive source data being made available and if we target that with picture we should also see that -- why is that not...
JASON: Maybe I -- maybe you can't style a picture that way? Yeah, it says it is like being picked up but it is not --
DANIEL: It is applying it but it is not actually the right size. There is the image within it.
JASON: I don't know. Maybe it is falling back to the -- well, if it was falling back to the image that would work too. Does it need to be source?
DANIEL: Let's try the source.
JASON: The mysteries.
DANIEL: CSS is mastering us at this point.
JASON: [Laughter] Yeah. So whatever we are doing it's weird. Oh, no. It is something to do with maybe they can't have data attributes or something? CSS ignores the data attributes on them?
DANIEL: That was weird. Go back. What did you get rid of?
JASON: I got rid of the scoped and it worked. A bug, everyone! [Laughter] No, this is totally fine because we know how to work it. What I can probably do to get away from this is I can add a class of image and then instead of one of these we will do one of these.
DANIEL: It won't yet work because it is expecting a variable called image. If we just remove that binding --
JASON: Oh, right. Of course.
DANIEL: That should work. Oh, for goodness sake.
JASON: We do have the thing here but it doesn't want to work if I add a new one.
DANIEL: It is still the scoped thing, I think. The issue is that the -- and this I think, I would have to investigate if this is a Vue bug but it seems to be basically the contents of this picture are not scoped properly. If we remove that scoped attribute which we can do now that we have a class.
JASON: I have a theory. I don't get it. I don't get why it's doing it.
DANIEL: I think the source isn't scoped.
JASON: Just escape? I don't know what's going on. OK. We know that this works, though, so I am just going to shimmy on do you know this because that's going to let us -- what?! What's happening?
DANIEL: What is happening? This is --
JASON: How did we get here?
DANIEL: You wanted max width to be set to all of those and I think that definitely worked at some point in time.
DANIEL: Honestly, building a project --
JASON: I am befuddled right now. We want max with 500. OK. We are -- [Laughter] -- I don't know. I don't know what happened but we are OK now. We have got our images and we have a source set and it is doing the thing. It looks like it has a max width ranging from 320 up to about triple pixel density which is good.
DANIEL: Now Nuxt does this out of the box. You don't have to give it any information but Nuxt can do this too. If we changed it through to Nuxt image and now parse sizes, just as you were going to do previously and actually say in this case, this can be a string, we are going to pass it a string of basically what we want the -- I think in this case we will just say 500 pixels.
JASON: 500 double pixel density and triple -- or is it different?
DANIEL: Let's see. I think we can set different sizes for different breakpoints. In this case --
DANIEL: If basically what we want to do is when it is small you do the widthh of the page you will do sm colon 100vw and no comma. Sorry. This is a custom Nuxt thing. And that should do what we want. It is now given us a source set there.
JASON: Does that mean it's going to do the thing here?
DANIEL: I would be surprised.
JASON: Wait. Why are you breaking down? What happened? We got an error. What is our error? Unknown word. What? Don't be weird.
DANIEL: It is being weird. Did we delete a style block entirely? This is a Nuxt plus Vite bug but when you delete a file block you will get this. It is very odd.
JASON: We have deleted the block and now we have our image source is setup, it's got the sizes max width, image doesn't have style applied and as we go here it isn't controlled for margins so we get a little side scroll but it is resizing to keep up with a 100% until we hit a certain point at which point it snaps to 500. It is great and doing all of that without any styles apply. We just told it what to do it here.
DANIEL: That's just the browser doing it with a source set. By the way, I would always recommend adding explicit height and width because they won't change I think the size -- in most cases if you have CSS the height and width don't change the size but give aspect ratio to the browser helping reduce CLS
JASON: Aspect ratio is 500 by 281.
DANIEL: So we can exactly set those.
JASON: And now it reserved the space for us so we don't get any layout shift. Good call and tip.
DANIEL: But the sizes thing is what's going to do it for you in Nuxt image. That's going to tell you what things to generate.
DANIEL: Depending on how it is being used.
JASON: Yeah. Daniel, I just realized we are over time so I have got to wrap us up here. This has been so much fun. We were able to get quite a bit done. We got our first Nuxt project setup, we got the page router going in our pages folder, we setup the ability to load data, we used the Nitro server to set-up some API end points, we used Zod to validate that data which was a nice bonus, we have got dynamic routing using the square bracket slug and setup images to auto optimize images and I feel like we barely scratched the surface but this was incredible and a lot of good things happening in here. Daniel, where else should people go if they want to learn more?
DANIEL: Definitely join the Nuxt Discord. Feel free to get in touch with me there, on Twitter or Mastodon if DMs are a helpful way. Nuxt.com is a wealth of information. There is lots of stuff there. I think that there are lots of videos and talks and things that might be useful. I have got some on my website but yeah.
JASON: Excellent. This episode like every episode has been live captioned. We have Maggie with us today from White Coat Captioning and our sponsors are Netlify, NX, New Relic and brand new Pluralsight. Thank you for making this show more accessible to more people. Subscribe on YouTube, Twitch, the newsletter on learnwithjason.dev and wherever you get the information and keep up on new episodes as they become available. Thank you all so much for hanging out with us. Daniel, thanks for taking time to teach us about Nuxt. We will see you all next time.
Closed captioning and more are made possible by our sponsors: