"Elegance never goes out of style"

-- Camille, the Steel Shadow

Building a Modern Blog with Next.js and Markdown

By Dylan3 min read

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:

  1. Push your code to GitHub
  2. Connect your repository to Vercel
  3. Deploy with one click
  4. Automatic deployments on every push

Best Practices

  1. Keep posts organized: Use consistent naming (kebab-case)
  2. Optimize images: Use WebP format when possible
  3. Add reading time: Calculate based on word count
  4. Implement search: Use client-side search with Fuse.js
  5. 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! 🚀