In 2023 I wrote Simple Blazor Blog, a short note about building this site as a Blazor WebAssembly app hosted on GitHub Pages. The main idea was simple: each post was a Blazor component, the app discovered those components at runtime, and a prerendering step produced static HTML so the site would behave more like a normal blog.
That post still describes the original shape of the project, but a lot of the implementation details are obsolete now. The site is still a static Blazor blog, but the way posts are authored, rendered, indexed, and deployed has changed quite a bit.
The Old Version
The 2023 version had two important constraints:
- Posts were written as
.razorcomponents. - The static output was produced with
react-snap.
The Razor approach worked, but it made writing feel too close to application development. Every paragraph needed component syntax, and simple prose carried more ceremony than it should. It was flexible, especially for interactive posts, but it was not a great default for regular writing.
The prerendering setup also worked, but react-snap was always a workaround. It was designed for React apps, and the blog needed extra configuration like skipThirdPartyRequests to keep the static generation reliable.
The "next steps" section of that post said I wanted markdown authoring. That is now done.
Markdown Is Now The Default
New posts can be plain markdown files with YAML frontmatter:
---
title: The Blazor Blog, Three Years Later
date: 2026-05-02
page: /posts/blazor-blog-three-years-later
description: A follow-up to the original Simple Blazor Blog post.
tags:
- blazor
- markdown
---
The file name still matters, but frontmatter now carries the human-facing metadata. The title, date, description, route, and tags are all explicit. That makes the archive and home page less dependent on guessing intent from a class name or file name.
The interesting part is that markdown is not parsed at runtime. The project has a C# incremental source generator that reads .md files during compilation, parses them with Markdig, and generates Blazor components.
That keeps the runtime model close to the original design. The blog still ends up with normal routed Blazor components. They just happen to be generated from markdown instead of handwritten as Razor.
Code Blocks Are Still Components
Markdown code fences are converted into the same CodeSnippet component used by the older Razor posts. That keeps syntax highlighting and copy behavior consistent across old and new posts.
[Generator]
public class MarkdownGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// Discover markdown files during compilation
// Parse frontmatter and markdown
// Generate routed Blazor components
}
}
This also means the site can keep improving the code block experience in one place. Syntax highlighting, light and dark palettes, copy buttons, and the small "Copied!" tooltip are all styling and client-library concerns, not authoring concerns.
Prerendering Moved To Playwright
The site no longer uses react-snap. The prerender step is now a small Node script built around Playwright.
The script does a few jobs:
- Discovers Razor and markdown posts from
Pages/Posts. - Writes
rss.xml,sitemap.xml, androbots.txt. - Starts a local static server from the publish output.
- Opens each route in Chromium.
- Saves the rendered HTML back into the static output.
- Copies the home page to
404.htmlfor GitHub Pages fallback routing.
This is more direct than the old setup. It understands this project, it does not need to pretend the app is React, and it lets the build fail if a route renders the not-found page.
Deployment Is More Explicit
The GitHub Actions workflow now does the full production build:
- name: Publish .NET Core Project
run: dotnet publish src/mohdali.github.io/mohdali.github.io.csproj -c Release -o Prerender/output --nologo
- name: Pre-render Blazor pages
working-directory: Prerender
run: |
npm install
npx playwright install chromium
npm run prerender
The generated Prerender/output/wwwroot folder is then deployed to the gh-pages branch. GitHub Pages still hosts a static site, but the static output is now produced by a build process that knows about the blog content.
Still Blazor, Still MudBlazor
The frontend is still Blazor WebAssembly and still uses MudBlazor. The recent work was not a framework rewrite. It was mostly a cleanup of the experience around the existing stack.
The site now has:
- A calmer home page with latest posts.
- A real posts archive grouped by year.
- RSS and sitemap output.
- Light and dark themes with a custom MudBlazor palette.
- Cleaner typography and spacing.
- Better code block colors.
- A less awkward copy button.
- Better handling for embedded interactive content.
Most of the visual changes are CSS and theme configuration on top of MudBlazor. That is a good place for this site to be. MudBlazor handles the component primitives, while the site stylesheet makes the blog feel like its own thing.
What Is Obsolete In The 2023 Post
If I were rebuilding this now, I would not start from the exact recipe in the old post.
I would still keep Blazor WebAssembly for static hosting. I would still keep the idea that posts become routed components. I would still prerender for fast first paint and crawlable pages.
But I would change these parts:
- Use markdown for ordinary posts.
- Use Razor posts only when a post needs custom interactive UI.
- Use a source generator instead of hand-authoring every post component.
- Use Playwright for prerendering instead of
react-snap. - Generate RSS and sitemap files as part of prerendering.
- Put post metadata in frontmatter instead of deriving everything from component names.
The old post was a good first version. The current version keeps the same spirit, but removes a lot of friction from writing and publishing.
The best test is this post itself: it is just a markdown file in the repo, and the build turns it into a routed, styled, prerendered page.