Build a static blog from markdown files with Next.js
Tags: tutorial, nextjs
Published: 2021-10-26 05:23:45

In this tutorial we’ll be building a blog using Next.js with the data for each of the individual blog posts loaded from markdown files. While there are many Next.js blog starter code bases available building from scratch is a great learning experience and shouldn’t take you long to get up and running.

Let’s get started by creating a new Next.js application:

```
npx create-next-app next-md-blog
```

We’ll also need a couple of dependencies so let’s go ahead and install those as well:

```
npm install gray-matter marked
```

* `gray-matter` – Parses front-matter from markdown files.
* `marked` – Compiles markdown into HTML.

Next let’s create a layout component that’ll load a global header and footer as is common practice when building a blog or websites in general. Create a new `components` folder with a `Layout.js`, `Header.js` and `Footer.js` files.

###components/Layout.js

```javascript
import Header from "./Header";
import Footer from "./Footer";

const Layout = (props) => (
  <>
    <Header />
    <main>{props.children}</main>
    <Footer />
  </>
);

export default Layout;
```

###components/Header.js

```javascript
import Link from "next/link";

const Header = () => {
  return (
    <header>
      <Link href="/">
        <a>Next.js Blog</a>
      </Link>
    </header>
  );
};

export default Header;
```

###components/Footer.js

```javascript
const Footer = () => {
  return (
    <footer>
      <p>&copy; {new Date().getFullYear()} - Powered by Next.js</p>
    </footer>
  );
};

export default Footer;
```

Next let’s create a sample markdown file for a blog post. This along with all subsequent blog post markdown files will need to be saved inside a new posts folder.

```
posts/hello-world.md

---
title: 'Hello World'
teaser: 'This is a short teaser for the first blog post.'
published: 'January 1, 2021'
thumbnail: '/images/stock-01.jpg'
---

Accusamus perferendis **voluptatibus** enim et. Cupiditate dolorum
delectus temporibus in [earum cumque](https://w3collective.com) sunt 
qui. Quia quidem dolores delectus ut beatae id. Totam eius labore ut. 
Dolores doloribus ut ab minima fugiat eum atque. Sit ullam vel itaque 
minima non officia sunt ab.
```

The images folder for the thumbnails should be located within the public folder in the root of the application. Create a couple more files like this and then we’re ready to create a `Post.js` component that’ll display teasers for each of these blog posts on the homepage.

###components/Post.js

```javascript
import Link from "next/link";

export default function Post({ post }) {
  return (
    <div className="post-teaser">      
      <Link href={`/blog/${post.slug}`}>
        <a><h3>{post.frontmatter.title}</h3></a>
      </Link>
      <img src={post.frontmatter.thumbnail} />
      <p>{post.frontmatter.published}</p>
      <p>{post.frontmatter.teaser}</p>
      <hr />
    </div>
  );
}
```

Here we’re getting the front matter data (title, teaser, published, thumbnail) and outputting that data inside some HTML markup. Now to start pulling everything together by updating the `pages/index.js` file as follows:

```javascript
import Head from "next/head";
import fs from "fs";
import path from "path";
import matter from "gray-matter";
import Layout from "../components/Layout";
import Post from "../components/Post";

export default function Index({ posts }) {
  return (
    <>
      <Head>
        <title>Next.js Blog</title>
        <meta name="description" content="A simple blog powered by Next.js" />
      </Head>
      <Layout>
        <div className="posts">
          {posts.map((post, index) => (
            <Post post={post} key={index} />
          ))}
        </div>
      </Layout>
    </>
  );
}

export async function getStaticProps() {
  const files = fs.readdirSync(path.join("posts"));
  const sortOrder = (a, z) => {
    return new Date(z.frontmatter.published) - new Date(a.frontmatter.published)
  }
  const posts = files.map((filename) => {
    const slug = filename.replace(".md", "");
    const markdown = fs.readFileSync(
      path.join("posts", filename),
      "utf-8"
    );
    const { data: frontmatter } = matter(markdown);
    return {
      slug,
      frontmatter,
    };
  });
  return {
    props: {
      posts: posts.sort(sortOrder),
    },
  };
}
```

Now all that’s left it to create the file to display the individual blog posts.

###pages/blog/[slug].js

```javascript
import fs from "fs";
import path from "path";
import matter from "gray-matter";
import marked from "marked";
import Head from "next/head";
import Layout from "/components/Layout";

export default function Post({
  frontmatter: { title, published, teaser },
  content,
}) {
  return (
    <>
      <Head>
        <title>{title}</title>
        <meta name="description" content={teaser} />
      </Head>
      <Layout>
        <h1>{title}</h1>
        <p>{published}</p>
        <div dangerouslySetInnerHTML={{ __html: marked(content) }}></div>
      </Layout>
    </>
  );
}

export async function getStaticPaths() {
  const files = fs.readdirSync(path.join("posts"));
  const paths = files.map((filename) => ({
    params: {
      slug: filename.replace(".md", ""),
    },
  }));
  return {
    paths,
    fallback: false,
  };
}

export async function getStaticProps({ params: { slug } }) {
  const markdown = fs.readFileSync(path.join("posts", slug + ".md"), "utf-8");
  const { data: frontmatter, content } = matter(markdown);
  return {
    props: {
      frontmatter,
      slug,
      content,
    },
  };
}
```

That’s all for this tutorial, you should now have a functioning blog using Next.js which you can easily add new posts using markdown files. We’ve only just scratched the surface of what’s possible with Next.js, we’ll be publishing many more tutorials on the topic so stay tuned.