How I Built a Markdown-Based Blog System for My Portfolio

Utkarsh Tiwari
5 min read
nextjsreactblogmarkdown

Table of Contents

markdown blogs

Introduction

I wanted to add a blog section to my portfolio where I could write technical posts and document my learning journey. After trying different approaches, I decided to build a markdown-based blog system using Next.js and MongoDB. Here's how I implemented it and what I learned along the way.

Why Markdown?

Initially, I considered using a rich text editor, but markdown felt more natural for technical writing. Plus, we're already comfortable with markdown from README files and documentation. It also gives me better control over formatting and makes it easy to include code snippets.

Setting Up the Data Model

First, I created a simple blog schema using Mongoose. I kept it straightforward since this was for my personal portfolio:

// models/blog.model.ts import mongoose, { Schema, Document, models, model } from 'mongoose'; export interface IBlog extends Document { title: string; slug: string; tags: string[]; author: string; content: string; // This stores the raw markdown createdAt: Date; } const BlogSchema = new Schema<IBlog>({ title: { type: String, required: true }, slug: { type: String, required: true, unique: true }, tags: { type: [String], default: [] }, author: { type: String, required: true }, content: { type: String, required: true }, createdAt: { type: Date, default: Date.now }, }); export default models.Blog || model<IBlog>('Blog', BlogSchema);

I decided to store the raw markdown content directly in the database instead of parsing it beforehand. This way, I can always edit the original markdown if needed.

Building the Admin Interface

Since this is my personal blog, I created a simple admin form where I can write posts in markdown. The key was using a textarea with monospace font to make writing markdown comfortable:

// components/admin/BlogForm.tsx <Textarea id="content" name="content" placeholder="Write your blog post in markdown..." value={form.content} onChange={e => setForm(f => ({ ...f, content: e.target.value }))} required className="font-mono min-h-[300px] text-sm" />

I added some basic styling to make it feel like a proper code editor. The monospace font makes it easier to align markdown syntax properly.

The Magic: Rendering Markdown

This was the most exciting part! I used react-markdown with react-syntax-highlighter to convert markdown to beautiful HTML with syntax highlighting:

// components/BlogContent.tsx import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism'; export default function BlogContent({ content }) { return ( <article className="prose prose-lg max-w-none"> <ReactMarkdown remarkPlugins={[remarkGfm]} components={{ code({ inline, className, children, ...props }) { const match = /language-(\w+)/.exec(className || ''); const language = match ? match[1] : ''; return !inline && match ? ( <SyntaxHighlighter style={oneLight} language={language} PreTag="div" {...props} > {String(children).replace(/\n$/, '')} </SyntaxHighlighter> ) : ( <code className={className} {...props}> {children} </code> ); } }} > {content} </ReactMarkdown> </article> ); }

The remarkGfm plugin was crucial for GitHub-flavored markdown support, which includes tables, strikethrough text, and task lists.

Styling Challenges

Getting the styling right took some experimentation. I used Tailwind's typography plugin (@tailwindcss/typography) as a base and then customized it:

/* globals.css */ .prose { max-width: none; } .prose pre { background-color: #f8f9fa; border: 1px solid #e9ecef; border-radius: 6px; } .prose code { background-color: #f1f3f4; padding: 0.2em 0.4em; border-radius: 3px; font-size: 0.9em; }

API Routes

I created simple API routes to handle CRUD operations:

// app/api/blogs/route.ts export async function POST(req: NextRequest) { await connectToDatabase(); const { title, slug, tags, author, content } = await req.json(); const blog = new Blog({ title, slug, tags, author, content, // Raw markdown stored here }); await blog.save(); return NextResponse.json({ success: true }); }

What I Learned

Building this system taught me several things:

Markdown is powerful: Once you get comfortable with markdown syntax, it's much faster than using a rich text editor, especially for technical content.

Component customization: The ability to customize how different markdown elements render gave me fine-grained control over the final output.

Performance considerations: Initially, I was parsing markdown on every render. I later optimized by memoizing the parsed content using useMemo.

SEO benefits: Since the markdown gets rendered to proper HTML, search engines can index the content properly.

Current Features

My blog now supports:

  • Full markdown syntax
  • Syntax highlighting for code blocks
  • Responsive design that works on mobile
  • Tag-based categorization
  • SEO-friendly URLs with slugs

Future Improvements

I'm planning to add:

  • Live preview while writing
  • Image upload functionality
  • Search functionality
  • Comment system (maybe using a service like Disqus)

Conclusion

This project was a great learning experience. It combined several technologies I wanted to explore and resulted in a practical addition to my portfolio. The best part? I can now write technical blog posts in markdown, which feels natural and efficient.

If you're working on your portfolio and want to add a blog section, I'd definitely recommend trying something similar. It's a good way to practice full-stack development while building something you'll actually use.

Share this article

Help others discover this content

Thanks for reading! 👋

More Articles