How I Replaced WordPress with a .NET Razor Pages Site in a Single Session
2026-03-19
My WordPress site had 122 blog posts, ~105 pages, a MySQL database, PHP, and a pile of plugins I hadn't touched in months. It was slow to update, expensive to maintain mentally, and felt like driving a school bus to the grocery store.
So I replaced it with a .NET Core Razor Pages app. No database. No CMS. No PHP. Just markdown files, C#, and Bootstrap. Here's the full playbook.
Why Leave WordPress?
WordPress is great — until it isn't. For a content-focused personal site, the overhead was hard to justify:
- Plugin fatigue — Security updates, compatibility issues, and plugins I installed years ago for features I no longer use
- Database dependency — MySQL running 24/7 to serve what is essentially static content
- Performance — Page load times were fine, but they could be great
- Cost — Not dollars (Lightsail is cheap), but time. Every WordPress update was a risk assessment
What I actually needed was a fast site that renders blog posts, drives readers to my book, and has a contact form. That's it.
The Architecture
I landed on ASP.NET Core 9 Razor Pages with a dead-simple content pipeline:
- Blog posts are markdown files with YAML frontmatter, stored right in the repo
- No database — a singleton service loads all posts into memory at startup
- Bootstrap 5 for styling (already bundled by the dotnet template)
- Markdig for markdown-to-HTML rendering
- YamlDotNet for parsing frontmatter
The entire "CMS" is about 100 lines of C# in a MarkdownService class. It reads .md files from a Content/posts/ directory, parses the YAML header, converts the markdown to HTML, and caches everything in a dictionary keyed by slug.
public class MarkdownService : IMarkdownService
{
private Dictionary<string, BlogPost> _posts = new();
// Loads all .md files at startup
// FileSystemWatcher reloads in dev mode
// Exposes: GetAll(), GetBySlug(), GetByCategory(), GetFeatured()
}
Each markdown file looks like this:
---
title: "AWS Explained: What is EC2?"
slug: "aws-explained-what-is-ec2"
date: 2023-06-15
category: "AWS"
description: "A beginner-friendly explanation of Amazon EC2..."
tags: ["aws", "ec2", "cloud"]
featured: true
---
Your content here in standard markdown...
Adding a new blog post means creating a .md file and deploying. That's it.
Preserving WordPress URLs
This was the most important technical requirement. I had 122 posts indexed by Google with URLs like natthompson.com/aws-explained-what-is-ec2. Breaking those would tank my SEO.
The routing strategy uses three layers:
- Static Razor Pages handle
/about,/blog,/contact, etc. /blog/{slug}is handled byBlog/Post.cshtmlwith an explicit routeMapFallbackToPage("/Slug")catches any/{old-wordpress-slug}and renders the post if found, returns 404 if not
A TrailingSlashMiddleware strips trailing slashes with a 301 redirect, so /aws-explained-what-is-ec2/ (WordPress style) redirects cleanly to /aws-explained-what-is-ec2.
// In Program.cs
app.MapRazorPages();
app.MapFallbackToPage("/Slug"); // Catches old WordPress URLs
Every old WordPress URL just works.
The Content Migration
WordPress has a built-in export tool, but since the domain was already pointing to the new site, I used WP-CLI directly on the server:
sudo wp export --path=/opt/bitnami/wordpress --dir=/tmp --post_type=post
Then a Python script (wp-export.py) converted the WXR XML into individual markdown files with frontmatter. The script handles:
- HTML to markdown conversion (headings, links, lists, code blocks)
- Category mapping from WordPress's sprawl into 6 clean categories
- Slug extraction for URL preservation
- Tag extraction
From 122 WordPress posts, I curated down to 79 keepers — cutting outdated content, duplicates, and off-brand posts.
Deploying to the Same Lightsail Server
Here's where it gets interesting. My WordPress site ran on a Bitnami Lightsail instance with Apache. Instead of spinning up new infrastructure, I deployed the .NET app alongside WordPress on the same server:
- Installed the .NET 9 ASP.NET runtime via the official install script (~45 MB)
- Published the app locally with
dotnet publishtargetinglinux-x64 - SCP'd the build to the server
- Created a systemd service running Kestrel on
localhost:5001 - Added an Apache vhost for
natthompson.comthat reverse-proxies to the .NET app
# /etc/systemd/system/natthompson.service
[Service]
WorkingDirectory=/home/bitnami/natthompson
ExecStart=/home/bitnami/.dotnet/dotnet NatThompson.Web.dll
Environment=ASPNETCORE_URLS=http://localhost:5001
# Apache vhost
<VirtualHost *:443>
ServerName natthompson.com
SSLEngine on
ProxyPreserveHost On
ProxyPass / http://localhost:5001/
ProxyPassReverse / http://localhost:5001/
</VirtualHost>
The existing Let's Encrypt SSL certificate required zero changes. WordPress is still on disk as a fallback — I just pointed the Apache vhost to the .NET app instead.
Total hosting cost change: $0. Same Lightsail instance, same price.
The Extras
A few features I added that WordPress gave me "for free" but were trivial to implement:
- RSS feed at
/feed— a minimal endpoint inProgram.csthat generates XML from the post list - Sitemap at
/sitemap.xml— auto-generated from all posts plus static pages - Contact form with AWS SES integration and reCAPTCHA v3 (invisible)
- Newsletter signup — embedded Kit (ConvertKit) form posting directly to their API
- Google Analytics — one
<script>tag in the layout - SEO meta tags — Open Graph and Twitter Card tags driven by each page's ViewData
The Results
| Metric | WordPress | .NET Razor Pages |
|---|---|---|
| Database | MySQL (always running) | None |
| Runtime | PHP + Apache | .NET + Kestrel behind Apache |
| Memory usage | ~860 MB | ~25 MB |
| Dependencies | 12+ plugins | 3 NuGet packages |
| Deploy process | FTP/SSH + pray | dotnet publish + scp + systemctl restart |
| Adding a post | Log into WP admin | Create a .md file |
| Hosting cost | ~$5/mo | ~$5/mo (same server) |
The memory drop alone is remarkable. WordPress with MySQL was consuming ~860 MB. The .NET app uses about 25 MB.
Would I Recommend This?
If you're a developer with a content-focused site and you're comfortable with C# — absolutely. The simplicity of markdown files in a repo is hard to beat. Version control for your blog posts. No database backups to worry about. Deployments are a one-liner.
If you're not a developer, stick with WordPress. It exists for a reason, and the ecosystem is unmatched.
The whole migration — from empty dotnet new to live production with 79 migrated posts — took a single working session. The hardest part was deciding which posts to keep.
The source code for this site is a standard ASP.NET Core 9 Razor Pages project. If you're interested in the technical details or want to do something similar, get in touch.