NextJS 13 Tutorial: Create a Static Blog from Markdown Files

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
Next.js is one of the most popular frameworks right now for web and front-end development. It's easy to use, blazingly fast, and you can pretty much make anything with it, from a simple static website to a full production-grade platform serving millions of requests a day. This year, it also received a major version upgrade to Next.js 13. This introduces a completely new way of doing things, which we are gonna learn by getting hands-on today and building a blog website from scratch. The actual blog posts, we're gonna keep them as markdown files or text files in a folder, and then we're gonna use Next.js to transform them into beautiful, statically-generated HTML pages. We're gonna style those pages using Tailwind CSS, then I'll show you how to host it for free on a platform called Vercel. If you've never used Next.js, React, or any front-end framework before, then I'm really excited for you because by the end of this video, you'll have a new and extremely useful skill to add to your portfolio. If you like the sound of that, then let's get started. To follow along with this project, you'll need to know a little bit of JavaScript, HTML, and CSS. You'll also have to have Node and the npm package manager installed. Here are the versions I'm using. You can find links on how to install them in the video description. I also recommend being familiar with Git and GitHub because that's how you can clone the example project I've provided, but also that's gonna be how we deploy the project eventually by the end of this video. Finally, you're gonna want to have a couple of images and markdown files to use as the content of this blog. If you have your own files already, then that's great, you can use those. Otherwise, feel free to download the ones that I've prepared for you already. The links are in the description. Now to help you understand what we're gonna be building today, here's a really high-level overview. We want to make an online blog that is fast to load, easy to edit, and cheap or free to host. Our content will be maintained as markdown files, with each file corresponding to a blog post. Our Next.js project will then use these files as input data to generate a bunch of static HTML and CSS files. And unlike traditional WordPress blogs, these files do not need a live web server to host. Instead, we can host them statically using a content delivery service, which you can sort of think of it as a cloud hard drive. To make changes to the blog, like edit a post or add a new post, we'll just make those changes directly to our source markdown files, and then we'll tell Next.js to rebuild the site. In fact, when we host it on Vercel, it will automatically do all of that for us every time we make any commits to the code, and it will redeploy the site with all the updated content. Now, Next.js 13 does introduce some major changes with how pages are routed and how data or content is fetched. But don't worry, it's all pretty straightforward, and we're going to cover that today. That pretty much summarizes our plan for this project. So let's go ahead and get started with the code. Let's go to our terminal and create the Next.js project. To do that, you'll type "npx create-next-app@latest", and then you'll be prompted to enter a name for your project. So I'm going to call this one Next.js blog, and I'm going to choose "yes" to using TypeScript and "no" to ESLint. After about a minute, the project should finish creating. So if you type "ls" in your terminal, you should see it appear as a directory. Go into that directory and then open this up in our editor. My editor is VS Code, so I'll open it up by typing "code .". And here is the project in our editor. If you're new to Next.js projects or React projects, then this is basically just all the files it comes with when you create a new project. There's a couple of configuration files and a few folders like pages, public, and styles to help us organize the project. To run the project as a website, go back to the terminal. Make sure you're in the same directory as the project, and then type "npm run dev". And then you should see a message like this, the server is ready, and it's on localhost:3000. So if you go to that URL inside your browser, you should see the website that's currently being served. With front-end projects like this, I usually like to have it alongside my editor, so I'll do a split view. But since Next.js has upgraded to version 13, they've introduced this new app directory instead of the pages directory. So the pages directory is kind of like the old stable way to do things. In the future, they'll slowly be moving towards this new app directory. Let's see how it works by actually getting hands-on with that. First, we're just going to delete our entire pages directory, because we're not going to need that anymore. So I'm actually going to stop my server first, just so nothing funny happens. And then back in my project, I'm just going to delete this. Once that's gone, I'm going to create a new folder, and then I'm going to call it app. And to create a page inside the app directory, we have to create a file called "page.tsx". And this is sort of a keyword name, so you can't just call this index or "home.tsx". It has to be called "page". And in this file, we're going to create a function that returns the page that we want to render. So once I have that, I should be able to run it and see this page show up instead of the old pages from my page directory. Let's go back to the terminal and run it again. This one fails because it says the app directory is experimental, and to enable it, we have to add "appdir: true" to the Next.js config under the "experimental" tag. So let's go ahead and do that. Go back to our folder, go to the Next.js config, and then add "experimental", and then we'll create "appdir: true". And when I run it again, it's working now, and you get a couple of warnings that it's an experimental feature. And when we run it the first time, you can see this message here. This is pretty cool as well. It says your "page.tsx" did not have a root layout. We created an app layout and a head for you. So if you go back to the projects, it's actually created two new files for us. There's a head file and there's also a layout file. And if you go back to the browser, you can see that the page is now working. So now we're ready to get started with this work. First, let's take a look at the files that we have and explain what they do. So you've got a "page.tsx" file. Now here in this app directory, this root "page.tsx" file is always going to be the index. But if I wanted to make another page, for example, another route on my server, I can create another folder and put another page.tsx inside of that. So let's see how that works. I'm going to create another folder inside app and I'm going to call this blog. And now inside blog, if I create another page.tsx, I've created another route for my website. But first I have to add this some content. So I'm just going to copy my home page here and paste it here. And I'll just call this blog page. And I'm going to change the content here to say "hello blog". So you can see the directory structure here is that there's an app folder and then the blog folder and inside the blog there's a page file. If I save that and go to my server and type /blog, I can now see this file being rendered. So basically the way that you route and create pages in Next.js 13 is all to do with your project structure and your project directory. Now I'm going to delete this blog because I don't actually need this page anymore. I just want the home page. So I'm just going to go back to my root directory there. And let's take a look at some of these other pages. So we've got a head.tsx. The head.tsx file basically corresponds to the HTML head element of this page. If you're not familiar with what that is, then you could just think of it as metadata for the page and loading other things in like fonts and icons and things like that. Here you could see that there's a title and it's empty. Nothing's showing up here. So let's actually add something in that title. I'm going to call it "Jack's Blog". And if I save it and then refresh it in the browser here, you should see that the title of the page in the tab has updated from being nothing to being Jack's blog. And the layout.tsx file basically creates a layout component that wraps whatever is inside this page. And it also wraps whatever is in the children of this app directory. So if you create more pages in here, they're all wrapped by this layout element. And this is a very common pattern, which is why probably Next.js decided to make it into an official thing, is that you generally have things like headers and footer elements and you want them to appear on every page. So you want to have a component that wraps all your inner content and that's exactly what it's doing here. So I think this is really useful. And we can see an example of that by just typing things in the body. For example, if I wanted to create a header element, I'll just do that right here in this layout. And here after I've added this, you can see these lines show up. This is a header and this is a footer and you can still see the content in the middle here. So to summarize what we're doing here is we're creating a layout that can wrap the content of each individual page that shows up in our website. And the page will be here in this children variable, which we're putting it right here in between the header and the footer. Now for this blog website, I want the header to show the title of my blog and I want the footer just to show like a message or a copyright or something. So let's go ahead and write that. And in fact, I'm going to turn the header and the footer into separate variables. So that's my header there. I basically just took most of what I've written here and just put it into a variable so that I could see it a little bit easier so that it's a bit more organized. And I'm going to do the same with the footer as well. So now I've got the header and footer for my site and I can work on the main content. By the way, the site's going to look pretty bad while we're building it out because we're not adding any CSS styling to it yet. So all the font sizes and all the spacing and everything will do that at the end. Right now we're just going to focus on the structure and the functionality of the site. Now, let's go back to the homepage. I don't want this to say hello from Next.js 13 anymore. I want it to actually return a list of blog posts that I have in my site. First, we need somewhere in our project to store all of our blog posts. So let's create a new folder and I'm going to call it post. And in this post folder, I've copied a bunch of markdown files that I've written previously from my blog. If you don't have any markdown files of your own, then you can use mine. You'll find the download link in the video description. Otherwise, you could also just write your own markdown files. So it's basically just a text file that ends with.md and you could just write it as plain text or you could use things like these hashtags to create headers and tables and code snippets and things like that. All my blog posts are written in markdown. So you can see them here and now they're just sitting in my post folder. So I want to turn these into a list of posts on the site. To do that, I'll go back to my homepage. So page.tsx, the one under app, and I want to create a function to actually read all these posts from this directory. I'm going to make a new function to do that. And I'm going to call it "getPostsMetadata". I'll need to import "fs", which is file system, and then I'll read the directory and I'll do it synchronously. So I don't have to make this an await function and then I get a list of these files. Now, let's make sure that we only grab the files that are actually markdown files. So like if you're on a Mac, you might have other files in here, like the ds store file, or you might have images in the post directory that you want to use, but we don't want any of that. We just want to show the actual post that end with ".md". And now I also want to return the slugs. The slugs is basically just this part of the name without the ".md". So this is going to be the name of the file, but also the name that I want to be in my URL when I want to actually visit that post. So that's called the slug and that's what we're going to return. And now in our homepage function, we can do post metadata equals get post metadata, and then we can create an element for each post in this list. And I'm going to call it "postPreviews" and we're going to go through each of these items and create a new div with a post title. So I'm just going to turn this return element into a div instead of h1 and then I'm going to put the post preview in curly brackets here because it's a typescript reference. And when I save that, I could see the post being refreshed here with my page showing each of the posts. Now, I want to make this clickable as well. So in Next.js, I can do that by adding a link element around this text. And to do that, I'll import link from next/link. So this up here and then I'll just do link and I'm going to do href, which is where I want to link it to and then I'm going to do /post and then I'm going to substitute the slug here for each post data because that's where I want it to go. And then when you close off the tag, we'll find that each of our elements now will link to something. So if I click on it, it will go to this page. So the URL is correct. It's going to localhost post and then this GPT3 generated poetry page, but nothing's showing up because we actually don't have those pages in our app yet. So what I want to do now is create a page for each of the post files that I have and I want the URL to be exactly what this slug string is over here. So if you remember from before to create a new directory or a new path inside our front end, we just have to create a folder under app and I'm going to want all of these posts to be under the post path. But how do I do this now? Because I mean I can create them individually one by one, but that's manual work and what I really want to happen is that every time I add a new Markdown file, I get a new path automatically created based on the name of this file. So I don't want to add these one by one. So how do I do it dynamically and make it really smart? Well in Next.js 13, we can actually use these square brackets to create dynamic paths. So here I'm going to create a square bracket and I'm going to type in "slug". This name doesn't matter, but here we're going to use this because that's what we're using in the rest of the project. And this is now sort of a variable that can be dynamic. So if we enter app, you know, if we go to our index/post/whatever, then any page that lands in here is going to have this available to it as a variable name. So in this folder now, in this post slug folder, we can create our page.tsx. And now if you click on any of these posts, it's no longer going to give you a 404. It's actually going to show this page here, which is this is a post and all of them are going to be the same at this point because we haven't done anything unique yet. But you can also see that the header and the footer are preserved throughout all these pages. So that's a really nice feature of Next.js 13. So how do we get access to this dynamic name? If we click on this, how do we get access to this part of the URL because we want to use that to load the actual contents of the blog. Well, there's a couple of different ways to do that. And one of the ways is that it actually gets passed in to this page function to this route. So if we declare a props parameter to capture any input here, we can actually access that information by doing const slug equals props.param, params.slug. And this name is whatever you've put inside the square brackets here. In this case, it's "slug". So it's going to show up as "params.slug". And then if I save that and then make that show up in our HTML, when I save and click on one of these posts again, you should see that the slug for this path is now accessible to this page. Now I want a way to get the contents of each of these markdown files as the post content and show it here. So I'm going to create a new function and I'm going to call it get post content and it's going to take the slug as the parameter. We're going to find the file for the slug. So we know that the folder is post. So we're going to do post/slug.md and we're going to read the contents of that file. So we have to import fs to do that again. "Read file sync" and that's the file path there and we're going to do it with UTF-8 encoding and we're just going to return that content. So let's use this in our post page now to display the content. And then when I click save that here, you see that the whole post content shows up. This is getting better. I mean the whole content is here, but it's not really readable. That's because the post itself was actually written in markdown and you can see like there's markdown tags here with the links and the tags and there's basically no style or formatting when we just load it as plain text. So how do we get markdown to render properly into our HTML? Well, the easiest way to do that is just to use a third-party plugin. The one I found here that was pretty useful is called markdown to JSX. It's got quite a lot of downloads and to install it, you can just go to the terminal and type npm i for install and then markdown to JSX. Once it's installed, let's go back to the post page and import it at the top of our file. So import markdown from markdown to JSX and this is actually a JSX element tag. So all we have to do is wrap our content in this markdown tag instead of this p tag here. And while we're at it, I'm going to change this p tag to a div tag. So we save that and we actually see that our blog is now formatted properly. So you can see links show up in blue, headers show up in bold and there's like a table here as well. So that's looking pretty good, but you might have noticed that the images are missing. That's because I've copied all the markdown files here, but I actually don't have the images that these are referencing. These images that are missing, it's referencing a folder called "images/" and then the name of the image, images/pico8 sprite editor, images/pico8 draw spite. There's a couple of them here. To make images available to our Next.js app, we have to put them into the public folder. The public folder will be the root of where your application will look for the images. So I'm going to click that and then click create folder and I'm going to call it images. And with the images for my blog, I've also prepared in a downloadable link a bunch of images that you can use. So make sure you download that first and then we'll just copy that and put it right into our images folder. And once you've copied all the images into public/images, save that and then hit refresh on this. And now the images should show up. Now the blog's starting to feel pretty good, but there's a couple of loose ends we still have to clean up. You'll see that there's this metadata at the start of each blog. We actually need a way to hide that and then use this data instead to show the title of the blog, like this title here, show the subtitle and maybe the date. So we're going to do that. But first we're also going to need a way to get back to our main page because right now I'm reading this blog post, but there's nothing for me to click that takes us back to the index page with the list of the blogs. So my plan is just to make this title, this "Jack's Blog" link at the top clickable and then that will take us to our index page. And that's really easy because in our layout, which is on every page, we have access to this header. So I'm just going to put a link element around this header. And I'll just have it linking back to the index. I'll have to import this from Next.js. So next import from next/link and that should show up in the top of the file here. And inside I'm going to put the title of the blog. And when I hit refresh now, I have this button as a link and I can click this to go back to my blog. And now I should be able to click on any post. I'll see the contents of the post formatted as markdown and I can scroll up and click this to go back to the home page. Now, let's look at solving the metadata problem. So with the post metadata, you can display the human readable form of the title. So this is the slug, but it's actually not a great title for each post. We can also display the post in chronological order with the date and also the subtitle or a summary of what the post is about. So that's all in the metadata of the post and that'll give us a much nicer browsing experience. So if you actually look at one of the MD files, the metadata is in this top section here and in markdown, this is called "front matter". The notation is we have these three dashes and then we've got a bunch of, I guess, almost like a YAML definition of some data and then we end it with another three dashes. So we don't actually want this as part of the content. We want to read this as metadata and we want to use these as variables in our website. And again, whenever we come across a difficult problem, the best thing to do is to find a third-party library that solves it. So in this case, the library I'm going to use is called gray matter and it lets us parse the front-matter string from a file. So this sounds like exactly what we need and it works with markdown files as well. So we're going to go to our terminal and install this by typing "npm i" and then "graymatter". When "graymatter" is done installing, we can go back to our index page and import it at the top of the file like this. Now instead of just reading the file title and returning the slugs, we can delete these last two lines and actually read the metadata content for each of these markdown posts. So here's the function that does that. Map through each of the markdown posts. We read the file contents using "readFileSync" and then we call this "matter" function on the entire file contents itself and then we'll return an object that makes some of this data more accessible to us. So I've got the title which we can access by typing "matterResult.data.title" and then "matterResult.data.date" and then the subtitle as well. All this data is basically all the stuff that we've defined here. So this can be anything you want it to be. We just have title, subtitle, and date and that's why it shows up here in our data. But we also want the slug still because the slug is what we're going to use for the paths later. So we're going to take the file name and replace the MD with an empty string. So that would be the object we get back from calling "getPostMetadata". Now I actually want to define this as an interface as well so that I'm not just returning like a key value pair here, but I actually have a model for the response that I'm returning. So let's create a folder to store that model and I'm going to call this components. And here I'm going to create a file called "postMetadata.ts" and it's not a TSX file because it doesn't return any JSX or any HTML element. It's just an interface. And in the file, I'm going to type export interface postMetadata. It's going to have a title. It's going to have the date. It's going to have the subtitle and it's going to have the slug. So basically all of the fields that we just saw earlier. And now instead of returning this sort of anonymous object for this, we can return a list of "postMetadata". Of course, I actually have to return the post here. I also have to import this as well. So if you're in VSCode, it should be smart enough to figure it out if you type quick fix or "Ctrl+.". And here it'll add the import line for me directly. So this is still erroring because we've changed the data model and we haven't updated it here in our homepage yet. So the "postMetadata" doesn't return just a slug anymore. It returns the entire "postMetadata" object. So I'm just going to call that post and then here I can do "post.slug". That can be here as well. So if I save that, now we're back to where we were before except that this post object, of course, as you saw actually has different fields attached to it. So we can put the title here now. And now we actually have the readable title of the post. We can also put the subtitle and the date. So now you can see that my posts actually have all the information I want. It's got the title of the post in a human readable form. It's also got the description. So the preview of each post. And when I click it, it's got the slug. So it will take me to the right part of the post. Now on the post page itself, we actually still see the metadata because of course that's shown in all the file contents, but we actually want everything outside of the metadata. So let's go ahead and fix that too. Go back to our posts page dot TSX inside the post slug directory. So here instead of returning the entire file content, let's also import gray matter from here because we can use that to read just the content of the file below the metadata that we've provided it. So we'll use gray matter to read the file and then from the matter result, we'll just use the content field. We should just have the content of the blog and not the metadata involved. So we'll click save and now you can see that the metadata part at the start has disappeared. So this is working as intended. Now, of course, I might want more than just the content of this post. For example, I might also want the date and the title in which case all I have to do is just return the entire matter result here from this get post content. And then I'm going to call this post instead and then here I can use "post.content". And here for the header I can use "post.data.title". So hit save and now I have both the title and the content displaying on each of my posts. So now it looks like the blog is mostly functional. Everything seems to be working properly, but there's actually still a major problem. Can you guess what it is? Every time we actually go to one of these paths, it turns out that this page has not been statically generated. What do I mean by that? I mean that with Next.js whenever it can, it tries to turn these JavaScript or TypeScript pages into static HTML into a file that doesn't have any compute involved to load. So there's no server. It's just an HTML file that you can load really fast. But these ones don't actually do that because right now our project has no way to know which of these names to generate statically. It just knows that if somebody goes to this directory, this post path and then puts some kind of string in here that it needs to show something on this page. So it needs for us to go here first before this is loaded. As a result, this is dynamically generated. For example, if I just put in any kind of string here, you could see that it tries to load the page, but it's a RuntimeError. That's because this page is still loading and that's when it tries to load the file and it finds that the file doesn't exist and that's why it fails. So this is actually a really bad problem, not just for pages that don't exist, but also for pages that do exist, these don't load as fast as they could if they were static. And if you want to see more proof of this, go back into your terminal where you're running the server and just hit Ctrl-C to terminate it and then write npm run build. This will actually build the website for deployment and it will tell you a list of things that are generated statically and a list of things that are generated for dynamic loading. So once you've run the build, you should see a result like this. These are all the pages created as a result of building the app and this is sort of like a key or a legend for each type of page that we've built. So we have a couple of static ones denoted by this circle and then this lambda sign, this lambda character, shows that it's something that renders on the server. So you can see that our 404 pages and our index page is statically generated and that's why it's a circle. But we have these slug pages that are generated on the server and we want to get rid of that because we want all our pages to be statically generated. Now, the strategy for doing that is we need to tell Next.js a list of all the paths we want to generate under this path. We could do that by just looking through our post directory and then coming up with all these slugs for potential paths to statically generate. Next.js has a "magic function" to help us do this and that function is "generateStaticParams". So this one needs to return a collection of slugs that we want to use for the generation of our paths. And the syntax basically looks like this. This is just one example and I've hard-coded the slug to be this "aws-quickstart". We want to return a list of objects where the key is whatever we've put here as the parameter. So in this case, it's slug, so it has to be the same here. Here are each of the values that we want to statically generate. To test this, if we go back to our terminal and run "npm build" again, you'll now see that our website has no more server functions, no more Lambda functions, everything is static. So this no longer needs a compute or a server to host. We can just host this statically in an S3 bucket on Amazon or Vercel or any of other static hosting, which is extremely cheap because we don't need a server. And you can actually see that we've generated this post/aws-quickstart, but that's the only one though. That's because we've hard-coded it. We actually want a dynamic way at build time to discover all of the markdown slugs and generate a post based on each of those. So instead of hard-coding it, we need to read this directory again and get all of this information. Luckily, we've already done something like that here with this get post metadata function in the index page. However, I don't want to duplicate this function though. So I'm going to move this function into the components as a function that we can import because it seems like we need it in two places. So I'm going to create a new file and I'm going to call it get post metadata. I'm just going to copy over that entire function and put it in here instead. I'll have to export the function as the default. And I have to copy over all the other imports that I needed. So now I don't need to import all of these things anymore. And instead in my index page, I can just import this get post metadata function I've created in my components. So I'll save that. Now in my post slug page, I can use that here as well. So I'll call get post metadata here and I'll have the metadata for all of my posts. And now I can use that to return each of these slugs as the generated static paths. So now if I go back to the terminal and hit "npm run build" again, you now see that when we build the project, it discovers each of the posts we've created in our Markdown directory and it turns them into a static page. So there's six pages in total and all of them are static. So I'm just going to go ahead and run the dev website again just to make sure everything still works. Now as one final cleanup task, I'd like to extract this post preview component, which is responsible for displaying these things here into its own file. Because with Next.js and React in general, you want each display element to be a separate component if possible. It just makes things a lot easier to test and a lot easier to work with when the project gets really big. So we already have a components folder. I'm going to create a new file and I'm going to call it "post preview." And this is capital because components should be capitalized in TypeScript notation. This is going to be a TSX file because it is going to be returning HTML. So I'm just going to create a post preview function and export it as the default here just as an empty starter. And then I'm going to go over to this thing and copy this entire div. This is going to be what I will return. So I'm going to return this. And now this needs some input. So it actually needs the post metadata because that's what we're giving it here as the input. So that's really easy. We're just going to do a props and it's going to be post metadata. And here we'll just replace each of these things. We'll also fix any missing imports. And now we can use this post preview as a separate component. So this is a lot cleaner because now we don't have components like this nested inside the homepage, which is especially useful if we want to reuse this in other pages as well eventually. So I'm just going to replace that with a post preview and I'll import it here. And this is how we use the post preview component we just created earlier. We have to pass in all the posts information. This "..." notation is called the spread operator, which means take all the fields of this item and make them the input parameter of this component. We could alternatively write it like this, that this repeats a lot of things. For example, "title: post.title", "subtitle: post.subtitle", and so on. If we have a lot of fields that we know are going to be duplicate of the item that we're passing in, we could literally just use the spread operator and it will do the same thing. Now, additionally, you might've noticed we have to add this "key" field as well. That's a requirement for if we are creating multiple elements inside the list. It's just that we need a way to tag them so that we know the ordering of them and when we need to re-render something. And the key must be unique. Sometimes it could just be a number, but it's better to use something like the post log, which we know is gonna be unique and accurately reflect which object these elements belong to. So let's just give our blog site a final test again. And I'm gonna click on a couple of posts here, see that they load properly. I can click back on my header. When I go to the blog pages, all the images are loading properly and everything's formatted properly. So that's pretty much it for the functional side of the project. We now have a blog that we can write and edit the content for just by adding these markdown files. And every time we run build or we build the blog, which we can later on do as an automatic process, it would generate a static HTML website based on the contents of these files. And since the site is static, you don't need a server to host it, you don't need to worry about patching, maintenance or security. It's really cheap to host and it's just a really low maintenance and really fast for the user as well because there's no server side involved. Now, if you're interested to make this look a little bit better, let's take a look at some styling options. The CSS framework I like to use with Next.js projects is called "TailwindCSS". So if you're not familiar with it, it lets you write CSS basically as classes, utility classes, and you can compose them directly in your JSX or your TSX files so you can change the way it looks without having to jump between your TSX files and your CSS files. And I find that a lot of the classes and configurations it comes with are really nice. So with Tailwind CSS, it's just really easy to make things look good. Let's see how we can use it to improve the look of our blog site. Go to tailwindcss.com and then hit search and type in Next.js. And you should find the instructions how to install Tailwind with Next.js here. So the first step is to create the Next.js project, but we've already done that, we already have our blog project ready to go. Now we want to install that Tailwind CSS library and a couple of other things. So we'll copy this command here, we'll paste it into our terminal, and then we can run it. Next, we need to go to our project and replace this module exports with this content field here. So go back to our project in VSCode and you'll see tailwindconfig.js and just replace that contents field with this one. And this basically tells Tailwind where to look for changes in these files and when to recompile the CSS. However, because this is a Next.js 13 project, we don't actually have this pages directory anymore. Instead, we're using this app directory. So I'm just gonna change that pages to app and we'll hit save. In the next step, we have to modify our global.css file with these directives. So we'll just copy all these three lines, go back to our project and then the "globals.css" and just replace everything with those three lines we just copied. Now, once you've added these lines to your global CSS, we actually need to import it into our app as well. Next.js used to do that by default, but in Next.js 13, I don't think that's included by default. So you have to go to "layout.tsx" and then just add that import style/global.css to the top of your file if you don't already have it. Now, if we go to "npm run dev", we should be able to use Tailwind directives directly in our project. So let's go ahead and try that out. I'm just gonna copy the snippet here, this class name and we'll put it into our header link here just to see if that works and I'll save it. Then I'll go to my terminal and run "npm run dev". And if I go back to my page, I see that all the default styling has gone and everything's just reset back to this basic font style. But this title, "Jack's Blog", is actually in bold and it's underlined because I did add these Tailwind things to it here. And we can also experiment with it and just see what else we can add. For example, I can make the text blue by just typing "text-blue-500" to choose a shade of blue. And if I save that and refresh the page, I can see that the text has turned blue. So now we're ready. Let's go ahead and get this page stylized so that it looks good. Let's first start off by centering this content in the middle of the page here so that it's not up against the side. I'll wrap my header content and folder inside a div. And for the div, I'm gonna set margin to "auto" so that if there's space on the left and right, it kind of puts the content in the middle. I also have to make that the content doesn't exceed a certain width for that to really show up. But I can't really see the size of this content because the divs are invisible. So let's add a border around it first. So I've added a red border to make the content stand out. And you can see that the content is the full size of this page. So I don't really want that. Let's give it a max width. And I'm gonna make the max width maybe 2XL, which if I highlight over this, by the way, to see these auto-complete and these hints, just go into the extensions marketplace and search for a Tailwind CSS extension. If you search for Tailwind, you should see this Tailwind CSS IntelliSense extension. And this is what gives you this nice auto-complete. So now I've set the max width to 2XL. And if I go back and refresh the page, I can see that the block content is now centered in the middle. Once I'm satisfied with that, I can take away the border. I don't need that anymore. Okay, so I want this header element to be in the middle. And I also want it to have a sort of a dark background so it stands out like a banner. So let's add that styling to this div in the header. First, we'll put text center to make it appear in the middle. And then we'll add bg-slate, which is sort of like a grayish color. And let's add "slate-800". So now there's a dark background to this header element. We'll also have to change the color of the text so that it actually can stand out against the slate color now. So for the title, I'm just going to make it white. And then for this part, this subtitle here, I'm going to make it a slate color as well. Except that I'm going to make it a really light slate. So maybe "slate-300". Let's put some padding so the elements aren't so jammed up at the top like that. I'll put a padding of 8 and that's going to apply to all sides. Now that looks a little bit better. But because we've actually put this whole thing into a container that's max width 2XL, it kind of crops out at the edge here. Now instead of trying to extend, I mean I could extend this all the way to the end, but I think it looks equally fine if I just kind of detach it from the top and make it sort of like a floating island banner. So let's try that. I'm going to give it some margin from the top. So that's a margin of about 6. And I'll also give it rounded corners just to make it a little bit softer. Okay, that's good. I had this line break earlier when I didn't have the style. But now that I have Tailwind, I don't need this anymore. So I'm going to delete that. I'll also give it margin at the bottom so that the posts aren't all crammed up against the banner here. But instead of doing margin bottom, and I already have a margin top, I could just erase both of these and do margin Y, which stands for the Y axis. So I do that once. And when I refresh it, I have this margin around the header element. Now the header is looking pretty good. Let's work on the footer. So for the footer, I probably want it in the center as well, and I want a line to divide it so it looks visually distinct from the rest of the post. So let's go ahead and do that. We'll add a border to our footer and then we don't need this line break there anymore either. And let's make the border a slightly darker color. So let's make it "slate-500". But I want a bit of space. So let's give it both a margin from the top, but also a padding from the top here. Okay, that looks pretty good, except that I don't want the border around the entire div. I just want the top line. So I'm going to change border to "border-t". And now I have to center my text. So I'm just going to type text center here as well. And I'll also make the text the same color as this border. I think this is a little bit too dark still, so I'm going to change the slate to 500 to 400, which makes it a little bit lighter. Refresh the page. Now I've got this nice looking header and this nice looking footer. And if I click on this, it takes me back to the home page. I think I'm going to make this font a little bit thicker though. So I'm going to go back to my header element here with text blog name and then just do font bold. Refresh that and now it shows up. Now let's go and improve each of these individual post elements. I'll open up the page TSX inside my post slug directory. Now if you remember from last time, we actually made each of these elements a post preview component. So we can go to the post preview component and edit this to update its style. First of all, I want to put each of the posts inside its own box. So it's just easier to see. This time I'm going to make the box a violet color just so that it stands out a little bit more. I also don't want it stuck against the edges. So I'm going to add some padding. I'll also make the box have rounded corners to match this header style. Let's also add a drop shadow just so it pops out a little bit more. Now because the boxes only have a border, they actually have a transparent background. So I see the shadow bleed through from the other elements. So I'm actually going to make the background explicitly white by adding "bg-white". Okay, that's looking a little bit better. Now I can make this text stand out and I want to make it have an underline when I highlight it with my mouse. So this element is this header too here. So that's where we're going to add our Tailwind classes. Firstly, we'll make the text bold and we'll also make the text this violet color. We'll also make it so that when we hover, we want to show an underline. So I can put this hover colon and that will apply the style only when we're underlining that text. So if I save and refresh the page, I can see that it's working like this here. Now, I think it makes sense if we actually put the date before the subtitle and make it a lot smaller because it's not that important. And for that, I'm going to put text SM, which stands for small and I'm also going to make it a lighter color. So I'm going to do text-slate-400. Okay, that looks pretty good. And I'm pretty happy with the size of the subtitle text, but I do want to make it a slightly lighter color just so that it looks more interesting than plain black. So I'm going to add slate to that as well. And I'm just going to make that slate-700. Okay, that looks pretty good now, but I think these borders are probably a bit too intense. So instead of "border-violet-600", I'm just going to make that a "border-slate-200" instead, just a very subtle light border around each box. So each individual element is quite nice, but I don't like how they're cramped up together like this. I can fix that by adding a margin to each of these boxes, but I think a better way to do that is actually go to the homepage and add a grid layout so that we can control the spacing between each of these. So I'm going to open this, go to my app, and then go to my page.tsx. Not the one in the post, but the one in the index of the app, in the root of the app itself. So let's go to that. And inside this div, I'm going to add a grid modifier. So I'll go to class name, and then I'll add a grid. And I want to say that I want to have one column in the grid, so it will maintain this structure, and that I want the gap between each element to be four. And by the way, this number here, this four, it doesn't literally mean four pixels. If I highlight it in my editor, I can actually see that it's one REM or 16 pixels. So the meaning of these numbers, they don't really map to any real-world measurement. They're just kind of a guide number to help you choose sensible sizes for your layout. So Tailwind will have gap one, two, three, four, and they'll just go up all the way to about a hundred, I think. And you kind of choose the number based on a rough approximation of how you want the layout to feel. So in this case, four is about 16 pixels. And if I refresh the page and look at it again, that starts to look pretty good. Except that when the screen is a certain size, I probably want these to have two posts in a row. Otherwise, I'm wasting a lot of space on the right here. So I can add a modifier to this so that if the screen is bigger than a certain size, for example, medium, I want the grid column to be two. And by the way here, medium width is 768 pixels, in case you were wondering. So if I refresh this page, now I can see that it's actually two columns. But if I change the size of the window when it's really small, like a mobile size, it's down to this column again. By the way, you might have noticed that in the mobile size, the side units are really cramped, like the edges. That's because I don't actually have any padding when it's this small. So I can go back to my layout file. And here, where I create the master div that holds all the content, I'll just add some padding to the sides. So I'll add some padding x of maybe 6. And here, 6 means 24 pixels. So I'll hit save, then refresh that. And now I have a little bit of space when the screen is really small, so it doesn't look too cramped. And of course, when the screen's bigger, I have my blogs as separate posts in two columns like this. So now our layout and our index page looks pretty good. We've got our header, our footer, and all our cards. But what happens when we click to view an actual post? Well, it turns out the layout is still pretty bad, because if you go back to our project and go to our post page, you notice that all the content is rendered inside this markdown element, which it spits out the HTML for us already without us having the ability to modify it with Tailwind. So how do we fix that? Well, it turns out that this is a pretty common problem, and Tailwind has already provided a solution for us. This is an official plugin called tailwind-css-typography, and it lets us render entire blocks of markdown or HTML elements that we don't have access to with some kind of styling like this. So we can just use it like this with an article class, and then we'll just add pros. To install it, we'll just copy this command over here, "npm install tailwind-css-typography", and put that into our terminal. And when it's done, we'll just add this require tailwind-typography to our plugins in the module exports. So we'll go back to our project, go to our tailwindcss.config, and then add that to our plugin. And now we should be able to follow the example and just wrap this article class around our entire markdown block. So let's copy that. Let's go back to our page with the markdown. And I'll add this article element that I copied. I'll change this to "className" because we can't use the word clas"s because that's reserved TypeScript. And then I'll put this markdown element inside this article. And when I refresh it, you can see that the post is formatted in a much nicer way. The color is a bit softer, highlighting for each of the links. There's highlighting for the headings. There's even block quotes, and there's a table. The table is being rendered properly, and there's even code examples. So just one easy line, and this whole thing is already looking a lot nicer. Of course, I still have this header element here that's outside of the markdown, which I've created myself with an H1 title. So we can just use tailwind to style that as normal. Now, what if we wanted to add some image elements to this blog? Well, as I mentioned before, if we want to add images to an Next.js app, we have to put them into our public folder. In fact, this one already has a couple of images. It's got the Vercel logo, which is the company that developed Next.js. And it's also got the favicon, which is this little icon that shows up here when you open up the website in a tab. So I'm going to delete those. And in this project description, I've actually attached some files, which you can use for this website, which I'm going to use for this blog. So it's going to be another favicon, a replacement one, and a logo image that we can use. So that's going to be a PNG file. So just copy those over into the public folder, not the public images that we created for our markdown, but just the base root public folder. And once they are there, you should be able to refresh the page. And after, it might take a couple of refreshes because you might have a cache, but eventually you should see that the favicon is updated to this pink swan. Now, I want to use this logo PNG as well. I want to put it right here at the top of the banner. So to do that, let's go back to our layout page. And in this header element, we're just going to put it right up here above the link. So Next.js actually has an image element tag that we can import. If you start typing an image, it will be under next/image. And then it needs a source argument. So for that, we're going to put /logo.png. And again, the /path is the public folder. So whatever is in the public folder, the "/" is going to be the root. We also have to specify a width and a height. Now, I think around 40 pixels is probably fine for this logo. And now we see the logo show up here, but it's all the way at this side. So I actually want to make it appear in the middle. And to do that, I can just use the same trick I used earlier, which is to add class name equals "mx-auto". So now it appears in the middle and that looks quite nice. Now, I'm going to do a couple more edits to this page just to make it look a little bit nicer. And I'm not going to comment on them, but you can see what I'm typing and see how they change the page. I'm just going to go through them quite quickly. Okay, so I've made a few updates there, just changed a few of the styles and move a couple of things around, just to get the blog looking where I think it should look to feel really nice and modern. And I didn't talk through them because I felt that that would have slowed down the process. So I just wanted to show how easy it is if you have an idea of what you want the website to look like in your head, how to get it there just using Tailwind CSS. So once you're happy with the site you developed locally, let's look at how to deploy it. Deployment of a static site is really easy because this whole site is generated as static HTML. We have a lot of different options. For example, AWS S3 is a very popular way. All the major cloud providers like Google and Microsoft all have a way to deploy static sites as well. However, my favorite way to deploy this is actually from a company called Vercel, which is the company that developed Next.js. If you go to Vercel.com, there's a button here on their homepage to basically just start deploying right away. So go ahead and click that and then you'll be given a choice to continue with a GitHub directory or a bunch of other ways to sign in. But my project is hosted on GitHub. So let's go ahead and click continue with GitHub. Of course, you have to make sure that any changes you made working on this project has been committed and pushed to your GitHub directory as well. Once you're ready, go to continue with GitHub and then it should show you a list of all your latest project directories. So select a blog that you've just pushed to your GitHub. In this case for me, it's Next.js blog tutorial. I'll click import and you can pretty much just leave all the settings as default because Vercel is the developer of Next.js. They know quite well how this project works. So we'll just click deploy right here and as it's deploying you can actually see the build logs. So if you expand the build logs, you can see every step Vercel is taking to build and deploy this website and you can see warnings and if it fails you can go into this build log and check the error message why it failed. And after about a minute, you should see this page. Congratulations, your site has been deployed successfully. And you can continue to the dashboard and look at the status of your site and the latest deployment and all the Git branches and everything. And if you click visit, it'll take you to the live deployment of your site. So this is the actual one that's been deployed on the public internet and the URL here as well is also publicly accessible. So you can share this with people and they'll be able to see your site. And this scales extremely well. It's really fast, really lightweight and it can handle a ton of traffic because it is a static site. It doesn't rely on a single server. So yeah, that's pretty much my favorite way to deploy a simple website like this or you can even deploy a complex site if you build it with Next.js this way. Congratulations. If you've made it this far, then you've learned how to build a static blog website using Next.js 13. You've also learned how to style it using Tailwind CSS and how to host it for free on Vercel. I hope you go on to build amazing things with this knowledge. If you've enjoyed this project, then please consider subscribing because there's more content like this coming up. And if you want to take your Next.js skills even further, then I recommend you check out this video where you'll learn how to add user authentication using Firebase to your Next.js project so that you can build a bigger service around it. Thank you for watching. See you next time.
Info
Channel: pixegami
Views: 47,840
Rating: undefined out of 5
Keywords: nextjs, nextjs 13, nextjs13, next js, next js 13, nextjs blog, nextjs tutorial, nextjs blog tutorial, nextjs 13 blog, static blog, nextjs markdown blog, react markdown blog, nextjs static site, nextjs static blog, nextjs tailwindcss, nextjs blog site, static blog tutorial, how to make static blog, nextjs 13 project, nextjs project, nextjs for beginners, learn nextjs blog, make nextjs blog
Id: Hiabp1GY8fA
Channel Id: undefined
Length: 53min 37sec (3217 seconds)
Published: Mon Dec 12 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.