22-12-2024
Creating a blog with Next.js and Markdown is an excellent way to combine the simplicity of static content with the power of a modern React framework. In this tutorial, we'll walk through building a fully functional blog using Next.js and Markdown files as the content source.
Markdown is a lightweight markup language that's easy to write and read. It simplifies content management by allowing you to write posts in plain text files. By pairing it with Next.js, you can:
Before starting, ensure you have the following installed:
Open your terminal and create a new Next.js application:
npx create-next-app@latest my-blog
cd my-blog
Start the development server:
npm run dev
Your project should now be running at http://localhost:3000.
Install dependencies for handling Markdown files and parsing them:
npm install gray-matter remark remark-html
Create a folder structure to organize your Markdown files and components:
my-blog/
├── posts/ # Markdown files for blog posts
├── pages/
│ ├── index.js # Homepage
│ └── [slug].js # Dynamic blog post pages
└── components/
└── Layout.js # Reusable layout component
Create a posts
folder in the root of your project and add some sample Markdown files:
posts/first-post.md:
---
title: "First Post"
date: "2024-07-01"
---
This is my first blog post written in Markdown!
posts/second-post.md:
---
title: "Second Post"
date: "2024-07-02"
---
Welcome to my second blog post. Markdown is awesome!
A Layout
component ensures consistency across pages:
components/Layout.js:
export default function Layout({ children }) {
return (
<div style={{ maxWidth: "800px", margin: "0 auto", padding: "20px" }}>
<header>
<h1>My Blog</h1>
</header>
<main>{children}</main>
<footer>
<p>© 2024 My Blog</p>
</footer>
</div>
);
}
Fetch the list of Markdown files and display their metadata on the homepage.
pages/index.js:
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
import Link from 'next/link';
export default function Home({ posts }) {
return (
<div>
<h2>Blog Posts</h2>
<ul>
{posts.map((post) => (
<li key={post.slug}>
<Link href={`/${post.slug}`}>
<a>{post.title}</a>
</Link>
<p>{post.date}</p>
</li>
))}
</ul>
</div>
);
}
export async function getStaticProps() {
const postsDirectory = path.join(process.cwd(), 'posts');
const filenames = fs.readdirSync(postsDirectory);
const posts = filenames.map((filename) => {
const filePath = path.join(postsDirectory, filename);
const fileContents = fs.readFileSync(filePath, 'utf8');
const { data } = matter(fileContents);
return {
slug: filename.replace(/\.md$/, ''),
...data,
};
});
return {
props: {
posts,
},
};
}
Use dynamic routes to generate pages for each Markdown file.
pages/[slug].js:
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
import { remark } from 'remark';
import html from 'remark-html';
import Layout from '../components/Layout';
export default function Post({ post }) {
return (
<Layout>
<h1>{post.title}</h1>
<p>{post.date}</p>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</Layout>
);
}
export async function getStaticPaths() {
const postsDirectory = path.join(process.cwd(), 'posts');
const filenames = fs.readdirSync(postsDirectory);
const paths = filenames.map((filename) => ({
params: { slug: filename.replace(/\.md$/, '') },
}));
return {
paths,
fallback: false,
};
}
export async function getStaticProps({ params }) {
const postsDirectory = path.join(process.cwd(), 'posts');
const filePath = path.join(postsDirectory, `${params.slug}.md`);
const fileContents = fs.readFileSync(filePath, 'utf8');
const { data, content } = matter(fileContents);
const processedContent = await remark().use(html).process(content);
return {
props: {
post: {
...data,
content: processedContent.toString(),
},
},
};
}
You can use CSS modules, Tailwind CSS, or any styling method you prefer to enhance the appearance of your blog.
Once everything is ready, deploy your blog to Vercel:
Congratulations! You’ve built a fully functional blog with Next.js and Markdown. You’ve learned how to:
Happy coding!