DEV Community

Paulund
Paulund

Posted on • Originally published at paulund.co.uk

How To Create A Blog With NextJS

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:

npx create-next-app my-blog
Enter fullscreen mode Exit fullscreen mode

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:

---
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 --
Enter fullscreen mode Exit fullscreen mode

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.

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);
}
Enter fullscreen mode Exit fullscreen mode

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.

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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.

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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.

export function getPostBySlug(slug: string): Post | undefined {
  return getPosts().find((post) => post.slug === slug);
}
Enter fullscreen mode Exit fullscreen mode

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()

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();
}
Enter fullscreen mode Exit fullscreen mode

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.

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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

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

  return posts;
}
Enter fullscreen mode Exit fullscreen mode

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

Top comments (3)

Collapse
 
mattlewandowski93 profile image
Matt Lewandowski

Another great option is using MDX. This allows you to map the markdown components to your design system. Really useful when you are using a lot of images or internal links and want to make use of the NextJS <Image/> or <Link/> components etc.

For example

export function useMDXComponents(components: MDXComponents): MDXComponents {
  return {
    // Allows customizing built-in components, e.g. to add styling.
    h1: ({ children }) => <h1 style={{ fontSize: '100px' }}>{children}</h1>,
    img: (props) => (
      <Image
        sizes="100vw"
        style={{ width: '100%', height: 'auto' }}
        {...(props as ImageProps)}
      />
    ),
    ...components,
  }
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
paulund profile image
Paulund

I like that idea, thanks Matt

Collapse
 
sketter1969 profile image
sketter1969

very good