How To Create A Blog With NextJS
Tags: react, nextjs, blog
Published: 2024-04-08 11:42:13

In this tutorial, we will walk through the steps to create a blog with NextJS.

## Set up a NextJS project
First, make sure you have Node.js installed on your machine. Then, open your terminal and navigate to the directory where you want to create your NextJS project. Run the following command to create a new NextJS project:

```bash
npx create-next-app my-blog
```

This will create a new directory called `my-blog` with a basic NextJS project structure.

The pages that we're going to create for this blog are:

- Home page
- Post page
- Tag Page

The content we're going to write in the blog will be in markdown format. We will add tags to each post to make similar posts easy to find on their own page.

## Create A Content Folder
Create a folder in the root of the project called `_posts`. This folder will contain all the markdown files for the blog posts. Inside the `_posts` folder, create a new markdown file called `first-blog.md` with the following content:

```markdown
---
title: My First Blog Post
slug: my-first-blog-post
tags:
  - NextJS
  - ReactJS
createdAt: 2024-04-08 12:00:00
---

-- Add your blog content here --
```

## Fetch Content API
Next, we need to create the api that will be used to fetch all the content from this `_posts` folder, we'll need to be able to sort them by date, search by tags
and search by slug.

Once we have the posts we want we'll need to convert this markdown into HTML to be displayed when you visit the post page.

Create a file in the root of the project inside `/lib/blog/api.ts` this is the file that we will use to fetch the content from the `_posts` folder.

The first page we're to create is the home page, this page will display all the posts in the `_posts` folder, we'll do this by creating a `getPosts()`
function.

```typescript
import { glob } from "glob";
import { join } from "path";
import fs from "fs";
import matter from "gray-matter";

// Create a variable for the location of the blog posts
const postsDirectory = join(process.cwd(), "_posts/blog");

/**
 * Using the glob package to get all the files in the _posts folder that have a markdown extension
 */
export function getPostSlugs() {
  return glob.sync("**/!(*.*).md", { cwd: postsDirectory });
}

/**
 * Get a single post
 * @param filepath - The path to the post
 * 
 * Uses the filepatch to get the contents of the markdown file. 
 * It will then use the gray-matter package to parse the front matter of the markdown file.
 */
export function getPost(filepath: string) {
  const fullPath = join(postsDirectory, filepath);
  const fileContents = fs.readFileSync(fullPath, "utf8");
  const { data } = matter(fileContents);
  return {...data, filepath};
}

/**
 * Get all posts
 * @param limit - The number of posts to return
 * 
 * Get all the posts in the _posts folder and sort them by date
 * Using the limit parameter we can decide how many blog posts to return
 */
export function getPosts(limit: number = -1) {
  const slugs = getPostSlugs();
  const posts = slugs
    .map((slug) => getPost(slug))
    .sort(
      (a, b) =>
        new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
    );

  return limit === -1 ? posts : posts.slice(0, limit);
}
```

Using the the above code on the Home page we can call the `getPosts()` function, this will then fetch all the blog post slugs,
fetch the content for each posts and sort them by date.

## Home Page
In NextJS we can create the home page by creating a file in the app directory called `page.tsx` from here we can simply call the `getPosts()`
function, loop through the results and display the posts.

```typescript
import { getPosts } from "../lib/blog/api";
import Link from "next/link";

export default function Home() {
  const posts = getPosts();

  return (
    <div>
      {posts.map((post) => (
        <div key={post.slug}>
          <h2><Link href={post.slug}>{post.title}</Link></h2>
          <p>{post.createdAt}</p>
          <p>{post.tags.join(", ")}</p>
        </div>
      ))}
    </div>
  );
}
```

## Post Page
The post page will only display a single post based on the slug, we can create this page by creating a foles in the app directory called `[slug]`
inside this folder we'll create the `page.tsx` file.

```typescript
import { notFound } from "next/navigation";
import { getPostBySlug } from "@/lib/blog/api";

type Params = {
  params: {
    slug: string;
  };
};

export default function Post({ params }: Params) {
  const post = getPostBySlug(params.slug);
  if (!post) {
    return notFound();
  }

  const content = await markdownToHtml(post.content || "");

  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.createdAt}</p>
      <p>{post.tags.join(", ")}</p>
      <div dangerouslySetInnerHTML={{ __html: content }} />
    </div>
  );
}
```

Here we take the params from the URL of the slug and send this to a function called `getPostBySlug()` this function will then fetch the post
by the slug parameter.

This function will go into the `lib/blog/api.ts` file we created earlier.

```typescript
export function getPostBySlug(slug: string): Post | undefined {
  return getPosts().find((post) => post.slug === slug);
}
```

If the post is not found then we'll need to return the 404 page.

If the post is found then we need to convert the markdown content to HTML, we can do this by creating a function called `markdownToHtml()`

```typescript
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import rehypeStringify from "rehype-stringify";
import rehypeShiki from "@shikijs/rehype";

export default async function markdownToHtml(markdown: string) {
  const file = await unified()
    .use(remarkParse)
    .use(remarkRehype)
    .use(rehypeShiki, {
      // or `theme` for a single theme
      theme: "github-dark",
    })
    .use(rehypeStringify)
    .process(markdown);

    return file.toString();
}
```

This is using a few packages in order to achieve this.

- `unified` - A tool for processing text using plugins.
- `remark-parse` - A markdown parser.
- `remark-rehype` - A markdown to HTML compiler.
- `rehype-stringify` - An HTML compiler.
- `rehype-shiki` - A syntax highlighter. Allowing us to highlight code blocks in the markdown.
- `@shikijs/rehype` - A syntax highlighter for rehype.

We will now be able to see the content of the post in HTML format.

## Tag Page
The tag page will be used to search through the posts by tags, we can create this page by creating a file in the app directory called `tags/[slug]`
inside this folder we'll create the `page.tsx` file.

This will create url of `/tags/reactjs` for example.

```typescript
import { getPostsByTag } from "@/lib/blog/api";
import { notFound } from "next/navigation";

type Params = {
  params: {
    slug: string;
  };
};

export default function TagPage({ params }: Params) {
  const tagSlug = params.slug;
  const capitalSlug = tagSlug.charAt(0).toUpperCase() + tagSlug.slice(1);
  const posts = getPostsByTag(tagSlug);

  if (posts.length === 0) {
    return notFound();
  }

  return (
    <div>
      <div className="bg-gray-200 p-8 mb-8">
        <h1 className="text-4xl">#{capitalSlug}</h1>
      </div>
      <div className="my-4">
        {posts.map((post) => (
            <div key={post.slug}>
                <h2><Link href={post.slug}>{post.title}</Link></h2>
                <p>{post.createdAt}</p>
                <p>{post.tags.join(", ")}</p>
            </div>
        ))}
      </div>
    </div>
  );
}
```

We need to create the `getPostsByTag()` function in the `lib/blog/api.ts` file.

```typescript
export function getPostsByTag(tag: string) {
  const posts = getPosts()
    // .map((slug) => getPost(slug));
    .filter((post) => post.tags?.includes(tag));

  return posts;
}
```

If there are no posts for the tag then we'll need to return the 404 page.

If there are posts for the tag then we'll display the posts.

## Conclusion
In this tutorial, we have walked through the steps to create a blog with NextJS. We have created the home page, post page, and tag page. We have also created a content folder to store all the markdown files for the blog posts. We have fetched the content from the `_posts` folder and converted the markdown content to HTML to be displayed on the post page. We have also added tags to each post to make similar posts easy to find on their own page.

This is just the starting point of creating a blog in NextJS, there are few extras that we will add to this blog at a later date such as:

- Pagination
- Search
- Comments
- RSS Feed
- Sitemap
- SEO
- Analytics