DEV Community

Michael Burrows
Michael Burrows

Posted on • Originally published at w3collective.com

Build a static blog from markdown files with Next.js

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

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

npm install gray-matter marked
Enter fullscreen mode Exit fullscreen mode
  • 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

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

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

export default Layout;
Enter fullscreen mode Exit fullscreen mode

components/Header.js

import Link from "next/link";

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

export default Header;
Enter fullscreen mode Exit fullscreen mode

components/Footer.js

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

export default Footer;
Enter fullscreen mode Exit fullscreen mode

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

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

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

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:

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

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

pages/blog/[slug].js

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

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.

Discussion (0)