Building MDBlog - A Markdown-First Blog System

Building MDBlog - A Markdown-First Blog System

The Problem

The journey started with a simple need: I wanted a blog where I could write posts with my preferred markdown editor, Markdown Monster by Rick Strahl, and just upload them via FTP to my server.

That's it. No complex CMS. No database setup. No admin panels. Just:

  1. Write markdown locally
  2. Upload via FTP
  3. Post appears automagically on the website

Sounds simple, right?

I started searching for existing solutions:

  • WordPress? Too heavy. Requires database setup, PHP, and a whole ecosystem.
  • Jekyll/Hugo? Static site generators are great, but require building and deploying the entire site.
  • Ghost? Still too complex with Node.js and database requirements.
  • Markdown CMS solutions? Most were either abandoned or overcomplicated.

Nothing matched the vision of "upload a markdown file and it just works."

The Decision

So I decided to vibe code this blog software!

And that's exactly what happened. With the power of GitHub Copilot and modern .NET, we built MDBlog from scratch in just one evening.

But here's the thing: It wasn't a one-shot generation. It was an iterative process - a dialogue between human and AI.

The Collaborative Process

This wasn't about asking AI to "build me a blog" and getting complete code. It was:

  • πŸ—£οΈ Discussing the vision and requirements
  • πŸ’­ Brainstorming architecture decisions together
  • πŸ”¨ Building feature by feature, step by step
  • πŸ› Finding errors together and debugging
  • 🎨 Refining the design through iterations
  • πŸ“ Documenting as we went

Each feature was a conversation:

"Hey, can we make the header collapse on scroll?"
β†’ Implemented CSS transitions
β†’ Tested together
β†’ Found performance issue
β†’ Fixed with passive listeners
β†’ Refined animation timing
β†’ Perfect!

Every challenge was solved together:

"Images aren't showing up correctly..."
β†’ Investigated the issue
β†’ Discovered path resolution problem
β†’ Tried regex solution
β†’ Tested various scenarios
β†’ Refined the approach
β†’ Problem solved! 🎯

This iterative, conversational approach is what made the difference. Not AI replacing the developer, but AI amplifying the developer's vision through collaborative problem-solving.

The Vision

Core Requirements

  1. Markdown-First: Posts are just .md files with YAML front matter
  2. FTP-Friendly: Upload files, restart app, done
  3. No Database Setup: SQLite for caching, auto-generated
  4. Beautiful Design: Inspired by bitboxx.net's matrix aesthetic
  5. Developer-Friendly: Built with Blazor and .NET 10

Non-Requirements

  • ❌ No admin dashboard needed
  • ❌ No online editor required
  • ❌ No complex authentication
  • ❌ No database migrations

The Architecture

Why Blazor + .NET 10?

Blazor was chosen because:

  • Modern, fast, and interactive
  • C# everywhere (no JavaScript mess)
  • Server-side rendering for SEO
  • Native .NET performance

.NET 10 because:

  • Latest and greatest
  • Amazing performance improvements
  • Future-proof

The File Structure

Each blog post lives in its own directory:

BlogPosts/
β”œβ”€β”€ 2024-11-20-building-mdblog/
β”‚   β”œβ”€β”€ post.md           # The content
β”‚   └── images/           # Post images
β”‚       β”œβ”€β”€ cover.svg

Why this structure?

  • Easy to organize
  • FTP-friendly (just upload a folder)
  • Date in directory name for sorting
  • Images live with the post

The Magic Sauce

Here's how it works:

  1. Background Service scans BlogPosts/ directory on startup
  2. YAML Front Matter is parsed for metadata
  3. SQLite Cache stores metadata for fast queries
  4. Markdown Rendering happens on-demand with Markdig
  5. Image Paths are automatically converted to absolute URLs
// This runs on startup
await dbContext.Database.EnsureCreatedAsync();
await blogService.RefreshPostsFromFileSystemAsync();

The beauty? Delete the database, restart the app, and it rebuilds from the markdown files! 🎯

Key Features Implemented

1. Workflow βœ…

Write your post in your preferred markdown editor:

---
title: "My Post"
date: 2024-11-20
author: "Torsten Weggen"
tags: [blazor, markdown]
image: "images/cover.jpg"
---

# My Post

Content here...

Save. Upload via FTP. Done! ✨

2. Syntax Highlighting 🎨

Using Highlight.js from CDN:

public class BlogPost
{
    public string Title { get; set; }
    public DateTime PublishedDate { get; set; }
}

Supports C#, JavaScript, Python, PowerShell, SQL, and more!

3. Theme System 🎨

  • Customizable themes - Switch between themes instantly
  • Default theme - Clean, professional blue & white
  • Bitboxx theme - Matrix-inspired with collapsing header
  • Easy switching - Just change config, restart, done!

Want your own look? Create a custom theme with just CSS, JavaScript (optional), and a config file!

4. Smart Image Handling

Write markdown like this:

![Photo](images/photo.jpg)

It automatically becomes:

<img src="/BlogPosts/2024-11-20-my-post/images/photo.jpg" />

No manual path management! πŸš€

5. Placeholder SVG Generation πŸ–ΌοΈ

No cover image? No problem! Auto-generated SVG placeholders:

<svg>
  <text>Blog Post Title</text>
</svg>

6. SEO Optimization

Every post gets:

  • Meta descriptions
  • Open Graph tags (Facebook)
  • Twitter Cards
  • Semantic HTML
  • Proper heading hierarchy

7. View Tracking

Simple analytics built-in:

  • View counts per post
  • Stored in SQLite
  • No external tracking needed

The Development Process

The Iterative Journey

This wasn't a linear "write code, done" process. It was iterative and conversational - each phase built on learnings from the previous one.

The pattern repeated:

  1. πŸ’¬ Discuss what we want to achieve
  2. 🎯 Plan the approach together
  3. πŸ’» Implement step by step
  4. πŸ§ͺ Test and find issues
  5. πŸ”§ Debug together
  6. ✨ Refine until perfect
  7. πŸ“ Document what we learned

Real examples from our sessions:

"Let's add syntax highlighting!"

  • First attempt: Added Highlight.js
  • Issue: Not applying to code blocks
  • Discussion: When does Blazor render?
  • Solution: JavaScript interop after render
  • Refinement: Proper lifecycle hooks
  • Result: Beautiful syntax highlighting! 🎨

"The bitboxx.net design looks cool!"

  • Started with basic layout
  • Tried matrix theme
  • Issue: Header too static
  • Idea: Make it collapse on scroll
  • Implementation: CSS + JS
  • Debugging: Performance issues
  • Fix: Passive event listeners
  • Polish: Smooth transitions
  • Final: Awesome animated header! πŸ’š

"Images aren't working..."

  • Problem identified together
  • Multiple solutions discussed
  • Regex approach chosen
  • Edge cases tested
  • Path conversion refined
  • Placeholder SVG added as bonus
  • Complete solution! πŸ“Έ

Phase 1: Core Structure

  • βœ… Blazor app setup
  • βœ… File system scanning
  • βœ… SQLite integration
  • βœ… Basic routing

Challenge: "How do we scan markdown files?"
Solution: Background service with file system watcher
Iteration: Added caching, error handling, logging

Phase 2: Markdown Processing

  • βœ… Markdig integration
  • βœ… YAML front matter parsing
  • βœ… Image path conversion
  • βœ… Code syntax highlighting

Challenge: "YAML parsing is tricky..."
Solution: YamlDotNet with custom deserializer
Iteration: Added fallbacks, validation, error messages

Phase 3: Design

  • βœ… bitboxx.net inspired layout
  • βœ… Collapsing header animation
  • βœ… Responsive design
  • βœ… Matrix theme

Challenge: "How to make it responsive?"
Solution: Media queries + flexible layout
Iteration: Tested on mobile, tablet, desktop - refined breakpoints

Phase 4: Theme System

  • βœ… Theme architecture
  • βœ… Default theme (generic)
  • βœ… Bitboxx theme (personal)
  • βœ… Theme switching

Challenge: "How to support multiple designs without code duplication?"
Solution: Theme system with CSS, JS, and config per theme
Iteration: Made Bitboxx theme private, Default public

Phase 5: Polish

  • βœ… Placeholder images
  • βœ… SEO optimization
  • βœ… Error handling
  • βœ… Mobile optimization

Challenge: "Posts without images look empty..."
Solution: Auto-generated SVG placeholders

Phase 6: Documentation

  • βœ… README.md with theme docs
  • βœ… THEMES.md for custom themes
  • βœ… IIS deployment guide

Challenge: "How to explain the theme system?"
Solution: Separate THEMES.md for developers, README for users

Iteration: Consolidated everything into README
Iteration: Structured, organized, complete guide

What Made This Work?

The dialogue was key:

❌ Not: "Build me a blog system" β†’ generates 10,000 lines of code
βœ… Instead: Step-by-step feature building with constant feedback

❌ Not: AI doing everything alone
βœ… Instead: Human vision + AI implementation + shared debugging

❌ Not: Accepting first solution
βœ… Instead: Iterating until it's exactly right

The power of "What if we..." questions:

  • "What if we make the header collapse?"
  • "What if we add a ticker with recent posts?"
  • "What if we auto-generate placeholders?"
  • "What if we use green like bitboxx.net?"

Each question led to exploration, implementation, and refinement.

Challenges Overcome

1. Image Path Resolution

Challenge: Relative image paths in markdown don't work when served from Blazor.

Solution: Regex-based path transformation after markdown rendering:

var imgRegex = new Regex(@"<img\s+([^>]*?)src=""(?!http|\/BlogPosts\/)([^""]+)""");
html = imgRegex.Replace(html, match => {
    var imagePath = match.Groups[2].Value;
    return $"<img {beforeSrc}src=\"/BlogPosts/{slug}/{imagePath}\"{afterSrc}>";
});

2. Code Block Syntax Highlighting

Challenge: Highlight.js needs to run after Blazor renders.

Solution: JavaScript interop with proper timing:

window.highlightCode = function() {
    document.querySelectorAll('pre code').forEach((block) => {
        hljs.highlightBlock(block);
    });
};

Called from Blazor after content renders! βœ…

3. Header Collapse Animation

Challenge: Smooth header animation on scroll without performance issues.

Solution: CSS transitions + passive scroll listener:

window.addEventListener('scroll', handleScroll, { passive: true });

4. Git Workflow

Challenge: How to track source code but not blog content?

Solution: Smart .gitignore:

# Ignore posts and database
BlogPosts/
*.db

# But keep structure
!BlogPosts/.gitkeep
!BlogPosts/README.md

The Result

A blog system that:

  • βœ… Works with Markdown Monster - Write locally, upload, done!
  • βœ… FTP-Friendly - Just upload folders
  • βœ… Auto-Regenerating - Delete database? No problem!
  • βœ… Beautiful Design - Theme system with multiple styles
  • βœ… Customizable - Switch themes or create your own
  • βœ… Developer-Friendly - Built with modern .NET
  • βœ… Lightweight - ~50MB deployment
  • βœ… Fast - SQLite caching, on-demand rendering
  • βœ… SEO-Ready - All the meta tags you need

Workflow in Action

Here's the actual workflow now:

1. Write in Markdown Monster:

---
title: "My New Post"
tags: [dotnet, blazor]
---

# Content here...

2. Save images in images/ folder:

2024-11-20-my-post/
β”œβ”€β”€ post.md
└── images/
    └── photo.jpg

3. FTP upload to server:

/BlogPosts/2024-11-20-my-post/

4. Restart app (or wait for next restart):

iisreset  # Or just recycle app pool

5. Post appears automatically! πŸŽ‰

No build step. No database migration. No admin panel. Pure simplicity!

Technical Highlights

Stack

Frontend:  Blazor Server + Interactive Components
Backend:   .NET 10 + ASP.NET Core
Database:  SQLite (auto-generated)
Markdown:  Markdig
YAML:      YamlDotNet
Syntax:    Highlight.js (CDN)
Themes:    Modular system (CSS + JS + Config)

Performance

  • Startup: Background scan, < 1 second for 100 posts
  • Page Load: < 50ms (metadata from SQLite)
  • Markdown Rendering: On-demand, cached in memory
  • Image Serving: Static file middleware, 7-day cache

Code Statistics

C# Lines:       ~2,500
Razor Lines:    ~800
CSS Lines:      ~1,200
JS Lines:       ~100
Total:          ~4,600 lines

Build Size:     ~50 MB
Deployment:     Single folder copy
Dependencies:   5 NuGet packages

What I Learned

1. Simplicity Wins

The simpler the architecture, the easier to maintain. No complex abstractions needed.

2. File System > Database

For blog posts, the file system IS the database. SQLite is just a cache.

3. Modern .NET is Amazing

Blazor + .NET 10 made this a joy to build. No JavaScript frameworks needed!

4. Good Documentation Matters

Spent as much time on README as on code. Worth it!

5. GitHub Copilot is Powerful

Pair programming with AI is the future. But it's not about AI replacing developers - it's about augmenting human creativity with AI capabilities.

What worked:

  • πŸ’¬ Constant dialogue - Not one-shot prompts, but ongoing conversation
  • πŸ”„ Iterative refinement - Each solution built on the previous one
  • 🀝 Shared debugging - Finding and fixing issues together
  • 🎯 Clear goals - Human sets the vision, AI helps implement
  • πŸ§ͺ Testing together - Catching edge cases and refining

The real power:

  • Writing code faster, but not blindly accepting it
  • Exploring solutions through conversation
  • Learning from each iteration
  • Debugging collaboratively
  • Documenting as we go

Example dialogue pattern:

Human: "Images aren't showing up"
AI: "Let me check the path conversion..."
Human: "Ah, it's relative paths!"
AI: "We could use regex to transform them"
Human: "Yes, but what about external URLs?"
AI: "Good point, let's exclude those..."
Human: "And what about already absolute paths?"
AI: "Added that check too!"
Human: "Perfect! Now it works!"

This back-and-forth, this collaborative problem-solving, is what made the difference.

Time saved? Massive! πŸš€
Quality? Higher, because we refined together! ✨
Learning? Continuous, through every iteration! πŸ“š

The future isn't AI replacing developers - it's developers + AI = superpowers! πŸ¦Έβ€β™‚οΈπŸ€–

Future Ideas

Things I might add later:

  • RSS Feed - For feed readers
  • Search - Full-text search across posts
  • Comments - Maybe Utterances (GitHub Issues)
  • Community Themes - Share custom themes
  • Draft Mode - Preview unpublished posts
  • Analytics Dashboard - Visualize view counts

But for now? It does exactly what I need! ✨

Try It Yourself

The project is open source:

GitHub: https://github.com/weggetor/MDBlog

Requirements:

  • .NET 10 SDK
  • That's it!

Setup:

git clone https://github.com/weggetor/MDBlog
cd MDBlog
dotnet run

Create a post:

mkdir BlogPosts/2024-11-20-hello-world
cd BlogPosts/2024-11-20-hello-world
# Create post.md with YAML front matter
# Add images/ folder if needed

Restart app, and it appears! πŸŽ‰

Conclusion

Sometimes you don't find the perfect tool because it doesn't exist yet.

Instead of compromising, we built exactly what was needed:

A lightweight, markdown-first blog system that works with Markdown Monster and FTP uploads.

No database setup. No build steps. No complexity.

Just write markdown, upload files, and blog! πŸš€

The Real Secret Sauce

But the bigger lesson here? It wasn't just about the code.

It was about the process:

  • Having a clear vision
  • Building incrementally
  • Iterating through dialogue
  • Debugging together
  • Refining until perfect

This is what AI-augmented development looks like:

  • Not "AI, build this"
  • But "AI, let's build this together"

Every feature was a conversation.
Every challenge was solved collaboratively.
Every refinement made it better.

The result? A blog system that does exactly what I need, built in record time, through iterative collaboration between human creativity and AI capabilities.


Built with:

  • ❀️ Passion for simplicity
  • πŸ€– GitHub Copilot (as collaborative partner)
  • πŸ’š bitboxx.net design inspiration
  • ✍️ Markdown Monster for writing
  • πŸ—£οΈ Constant dialogue and iteration

The Process:

  • πŸ’­ Vision from human
  • πŸ’» Implementation with AI
  • πŸ› Debugging together
  • ✨ Refinement through conversation
  • πŸ“ Documentation as we went

Tech Stack:

  • Blazor + .NET 10
  • SQLite + Entity Framework Core
  • Markdig + Highlight.js
  • YamlDotNet
  • Theme System (modular CSS/JS)

Status: Production-ready and powering this blog! ✨

Development Time: A few hours of iterative collaboration
Lines of Code: ~4,600 lines of refined, tested code
Developer Joy: Maximum! 😊


This post was written in Markdown Monster, uploaded via FTP, rendered by MDBlog, and documents the iterative process of building it. Meta²! 😊

Want to try it? Clone the repo, write some markdown, and experience the simplicity yourself!

Have ideas? Open an issue, start a discussion, or fork and improve! This was built through collaboration - let's keep that spirit going! πŸš€

An unhandled error has occurred. Reload πŸ—™