Skip to content

Building a blog with Next.js

  • react
  • nextjs
  • typescript

One of the most important frameworks for React applications is now Next.js. It enables developers to create better React applications without complexity for server-side rendering.

Today, the greatest alternative for people who want a simple but effective blog—without having to write a lot of code and while improving our SEO is to create one using Next.js.

In this tutorial, we'll use Next.js to create a static-generated, production-ready blog. We'll go through the process of static site generation (SSG) and create a fantastic blog with strong SEO.

Getting started

We'll start by creating a new Next.js application, To get started, we'll use the following command:

npx create-next-app@latest site --typescript

we'll structure our application, Our application structure will be like the following:

- site
  | - lib
      | - mdx.ts
      | - types.ts
  | - pages
      | - blog
          | - [slug].tsx
      | - _app.tsx
      | - _document.tsx
      | - blog.tsx
  | - posts
      | - 🎉.mdx

Now, we'll install all the dependencies that we'll need.

npm i --save-dev @types/node gray-matter next-mdx-remote rehype rehype-autolink-headings rehype-code-titles rehype-prism-plus rehype-slug remark-gfm

we'll write the code for the mdx.ts file.

lib/mdx.ts
import { serialize } from 'next-mdx-remote/serialize';
import remarkGfm from 'remark-gfm';
import rehypeSlug from 'rehype-slug';
import rehypeCodeTitles from 'rehype-code-titles';
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
import rehypePrism from 'rehype-prism-plus';

export async function mdxToHtml(source: string) {
  const mdxSource = await serialize(source, {
    mdxOptions: {
      remarkPlugins: [remarkGfm],
      rehypePlugins: [
        rehypeSlug,
        rehypeCodeTitles,
        rehypePrism,
        [
          rehypeAutolinkHeadings,
          {
            properties: {
              className: ['anchor']
            }
          }
        ]
      ],
      format: 'mdx'
    }
  });

  return {
    html: mdxSource
  };
}

A brief description of what's happening inside this file and what each function is doing is given below:

  • serialize is allowing MDX to be loaded within getStaticProps or getServerSideProps and hydrated correctly on the client.
  • remarkGfm

    This package is a unified (remark) plugin to enable the extensions to markdown that GitHub adds: autolink literals (www.x.com), footnotes ([^1]), strikethrough (~~stuff~~), tables (| cell |…), and tasklists (\* [x]). You can use this plugin to add support for parsing and serializing them. These extensions by GitHub to CommonMark are called GFM (GitHub Flavored Markdown).

  • rehypePlugins This option puts all the extras you want like:
    • rehypeSlug gives headers ids. It searches for headings (h1 through h6) without ids and adds id attributes to them based on the content included in those headings.
    • rehypeCodeTitles plugin to add code title.
    • rehypePrism plugin to highlight code blocks in HTML with Prism (via refractor) with additional line highlighting and line numbers functionalities.
    • rehypeAutolinkHeadings plugin to add links to headings with ids back to themselves and you can change the name of the className of the links

we'll create a types.ts file inside of our lib folder, where we'll construct all of our TypeScript interfaces and types. Put the following code inside the file:

lib/types.ts
import { MDXRemoteSerializeResult } from 'next-mdx-remote';

export interface PostPageType {
  slug: string;
  frontmatter: {
    title: string;
    description: string;
    date: string;
  };
  content: MDXRemoteSerializeResult;
}

export interface BlogPostType {
  [key: string]: Array<PostPageType>;
}

Rendering an article as a preview

First, we'll write data into the posts/🎉.mdx file:

posts/🎉.mdx
---
title: party
description: We'll celebrate now.
date: 2022-08-31
---

The getStaticProps function in Next.js will be used to retrieve all of our blog posts and inside the function, matter will be used to parse front-matter from a string or file into the blog.tsx page. The following code will now be pasted into our blog.tsx file:

pages/blog.tsx
import { readdirSync, readFileSync } from 'fs';
import { join } from 'path';
import matter from 'gray-matter';
import Link from 'next/link';
import { BlogPostType } from 'lib/types';

export default function Blog({ posts }: BlogPostType) {
  return (
    <article className="wrapper">
      <h1>Blogs</h1>
      <div className="margin-top-700">
        <ol className="auto-grid" role="list">
          {posts.map((post, index) => {
            return (
              <li className="card" key={index + 1}>
                <article className="flow">
                  <h3 className="fs-600">{post.frontmatter.title}</h3>
                  <p className="fs-300">{post.frontmatter.description}</p>
                  <Link href={`/blog/${post.slug}`}>
                    <a className="button">Read More</a>
                  </Link>
                </article>
              </li>
            );
          })}
        </ol>
      </div>
    </article>
  );
}

export async function getStaticProps() {
  const files = readdirSync(join(process.cwd(), 'posts'));
  const posts = files.map((filename) => {
    // Create slug
    const slug = filename.replace('.mdx', '');
    const markdownWithMeta = readFileSync(
      join(process.cwd(), 'posts', filename),
      'utf-8'
    );
    const { data: frontmatter } = matter(markdownWithMeta);
    return { slug, frontmatter };
  });

  return {
    props: {
      posts: posts
    }
  };
}

Rendering our blog posts

First, we'll import whoever matter, mdxToHtml and MDXRemote in [slug].tsx page, and use getStaticPaths and getStaticProps.

  • In the getStaticPaths function we'll fetch the post file paths
  • We'll retrieve slug as a property in the getStaticProps function to get post file paths for use in matter to bring frontmatter and content

The following code will now be pasted:

pages/blog/[slug].tsx
import { readdirSync, readFileSync } from 'fs';
import { join } from 'path';
import matter from 'gray-matter';
import { mdxToHtml } from 'lib/mdx';
import { MDXRemote } from 'next-mdx-remote';
import { PostPageType } from 'lib/types';

export default function PostPage({
  frontmatter: { title },
  content
}: PostPageType) {
  return (
    <article className="wrapper">
      <div className="[ post ] [ flow ]">
        <h1>{title}</h1>
        <hr />
        <MDXRemote {...content} />
      </div>
    </article>
  );
}

export async function getStaticPaths() {
  const files = readdirSync(join(process.cwd(), 'posts'));
  const paths = files.map((filename) => ({
    params: { slug: filename.replace('.mdx', '') }
  }));
  return { paths, fallback: false };
}

export async function getStaticProps({ params: { slug } }) {
  const markdownWithMeta = readFileSync(
    join(process.cwd(), 'posts', slug + '.mdx'),
    'utf-8'
  );
  const { data: frontmatter, content } = matter(markdownWithMeta);
  const { html } = await mdxToHtml(content);
  return {
    props: { frontmatter, slug, content: html }
  };
}

Conclusion

Using Next.js to create a blog is really simple and uncomplicated. There are several advantages to using Next.js, particularly for blogs. Your blog application will run very well, be lightweight, and have a high SEO ranking.