Building a modern blog doesn't have to be complicated. In this tutorial, I'll walk you through creating a fast, SEO-friendly blog using Next.js 15 and Markdown files.
Why This Tech Stack?
Next.js 15 with App Router
Next.js provides an excellent foundation for blogs:
- Server-side rendering for better SEO
- Static generation for blazing-fast performance
- App Router for intuitive routing and layouts
- Built-in optimization for images and fonts
Markdown for Content
Using Markdown files offers several advantages:
- Version control friendly - Track changes with Git
- Portable - Easy to migrate to any platform
- Developer-friendly - Write in your favorite editor
- No database needed - Simplifies deployment
Project Structure
Here's the recommended structure for a Next.js blog:
my-blog/
├── app/
│ ├── layout.tsx # Global layout
│ ├── page.tsx # Homepage
│ └── blog/
│ └── [slug]/
│ └── page.tsx # Dynamic blog post page
├── content/
│ └── blog/
│ └── *.md # Your Markdown posts
├── components/
│ ├── PostCard.tsx # Blog post card component
│ └── MarkdownRenderer.tsx
└── lib/
└── markdown.ts # Markdown parsing utilities
Key Implementation Details
1. Parsing Markdown with gray-matter
The gray-matter
library helps parse frontmatter metadata:
import matter from 'gray-matter';
const { data, content } = matter(fileContents);
// data contains frontmatter
// content contains the markdown body
2. Converting Markdown to HTML
Use remark
for Markdown processing:
import { remark } from 'remark';
import html from 'remark-html';
const processedContent = await remark()
.use(html)
.process(content);
3. Dynamic Routes with generateStaticParams
Pre-render all blog posts at build time:
export async function generateStaticParams() {
const posts = await getAllPosts();
return posts.map((post) => ({
slug: post.slug,
}));
}
Performance Optimizations
Image Optimization
Always use Next.js Image component:
import Image from 'next/image';
<Image
src="/hero.jpg"
alt="Hero image"
width={800}
height={400}
priority
/>
Metadata for SEO
Implement dynamic metadata for each post:
export async function generateMetadata({ params }) {
const post = await getPostBySlug(params.slug);
return {
title: post.metadata.title,
description: post.metadata.description,
openGraph: {
title: post.metadata.title,
description: post.metadata.description,
type: 'article',
},
};
}
Styling with TailwindCSS
TailwindCSS v4 provides excellent typography support:
<article className="prose prose-lg mx-auto">
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
Deployment with Vercel
Deployment is straightforward:
- Push your code to GitHub
- Connect your repository to Vercel
- Deploy with one click
- Automatic deployments on every push
Best Practices
- Keep posts organized: Use consistent naming (kebab-case)
- Optimize images: Use WebP format when possible
- Add reading time: Calculate based on word count
- Implement search: Use client-side search with Fuse.js
- Add RSS feed: Help readers subscribe to your content
Conclusion
Building a blog with Next.js and Markdown provides the perfect balance of simplicity and power. You get excellent performance, SEO benefits, and a great developer experience without the complexity of a traditional CMS.
The best part? Your content lives in version control, making it portable and future-proof.
Happy blogging! 🚀