How I Migrated My Blog from WordPress to Blazor WebAssembly
Introduction
I had been thinking about moving my blog away from WordPress for several years. As a .NET developer, I wanted to build the site on a platform I was more comfortable with. I also wanted a platform where I could demonstrate how the code I write about works in practice.
I chose Blazor WebAssembly rather than Blazor Server partly because I did not want to pay for an App Service Plan to host the application. Blazor WebAssembly could run as a client-side application and be hosted as a static site. That introduced another problem, though: search engines, AI bots, and other automated consumers should be able to discover the content without running the application in a browser.
I have written about that problem in my previous article about adding static page generation to Blazor WebAssembly . That article introduces Blazorade.StaticPages , which generates static HTML for content in a Blazor WebAssembly application.
You are now reading this article on that new blog. It runs on Blazor WebAssembly in Azure Static Web Apps, with Blazorade.StaticPages generating the static HTML.
Choosing the Authoring and Hosting Model
I decided to write new articles as Markdown documents. Markdown is readable as plain text and keeps the focus on writing and reviewing content instead of HTML markup. It also works well with source control, so the article history becomes part of the code repository.
The publishing workflow I built for this blog publishes those Markdown documents as
.razor
pages. This workflow is specific to my blog and is not part of
Blazorade.StaticPages
. It lets the pages use the Blazor components and metadata required by the site while keeping Markdown as the authoring format.
Blazor WebAssembly was a practical fit for the site because it allowed me to avoid an App Service Plan and use static hosting. The trade-off was that a client-side application alone does not provide the static HTML that crawlers need. Solving that problem was an important part of the architecture, not an optional detail added later.
The same general challenge exists for other single-page application technologies, including React, Angular, and Vue.js. The important part is not the framework itself, but making content available in a form that does not depend entirely on client-side rendering.
Migrating the WordPress Content
I started with the WordPress posts sitemap, which gave me the article URLs I wanted to move. I then used the WordPress REST API to extract the article content and metadata. The migration also needed to account for categories, tags, featured images, images embedded in articles, links, and other information required by the new site.
At first, I gave GitHub Copilot too much responsibility. I created a prompt that instructed it to import an article, convert it to Markdown, and download all the images used in the article. I did not notice initially that Copilot was also rewriting and shortening many of my articles. That was not what I wanted. I wanted to preserve the content from my previous blog and store it as Markdown.
That approach was too non-deterministic, so I changed tactics. I asked GitHub Copilot to write a PowerShell script that performed the import instead. I ran tests, made adjustments to the script, and tried again. After a few iterations, every import produced a deterministic and repeatable result, and the script was ready to use for the migration.
I began with one article at a time and then moved to small batches. This made it easier to find problems and adjust the process before they affected a larger number of files. The PowerShell script handled the conversion details, including removing tables of contents from old articles and converting WordPress syntax highlighting into standard Markdown code fences.
The Markdown preview in Visual Studio Code was useful during this work. I reviewed the migrated documents to check headings, links, images, lists, code blocks, and content that might otherwise have been lost during conversion.
The main lesson was to improve the workflow on a small sample before scaling it up. A large batch may be faster once the process is stable, but it makes mistakes harder to isolate. The right approach depends on the size and consistency of the source content.
From Markdown to Published Pages
After the content was migrated, my publishing workflow converted the Markdown documents into
.razor
pages. It then marked the appropriate Razor pages as containing static content.
Blazorade.StaticPages
looks at those marked Razor pages and generates static HTML for them as part of publishing.
This separates authoring from presentation without forcing the site to choose between an interactive application and crawlable content. Markdown is used for writing and reviewing, while the publishing workflow turns that content into Razor pages. Razor and Blazor provide the site structure and interactive behaviour, and static page generation makes the article content available before a browser has to run the WebAssembly application.
The publishing workflow described in this article is not part of the Blazorade.StaticPages package. The package provides static page generation. The Markdown authoring conventions, migration tooling, and publishing process are specific to my blog application. I may create a separate package for that workflow in the future, but it is not currently part of Blazorade.StaticPages.
Deploying to Azure Static Web Apps
I deploy the Blazor WebAssembly application to Azure Static Web Apps . It provides a hosting model that fits a static site and works with the generated pages.
One reason this setup appeals to me is the cost. The blog is currently running for free within the applicable Azure Static Web Apps free quota. That does not mean Azure hosting is universally free or that the terms will never change. Usage limits, product terms, and Azure pricing can change, so the current quota and pricing should be checked before relying on this for another site.
The migration also required preserving the blog's existing URLs and keeping the published content consistent with the new structure. These details are easy to underestimate. A migration is not complete merely because the new pages render. Existing links, article metadata, images, and the overall reading experience need to be checked as well.
Lessons Learned
The migration became easier once I treated it as two related problems: moving the existing content and choosing a better publishing model for future articles. The PowerShell workflow helped preserve the old content, Markdown solved the authoring problem, and Blazorade.StaticPages addressed the difference between an interactive Blazor WebAssembly application and the static HTML that crawlers need.
The most important practical lesson was to keep a human review step in the workflow. Automation and GitHub Copilot made the repetitive parts a lot faster, but they did not replace editorial decisions or verification. Small migration batches made it possible to improve the process without repeatedly correcting the same mistakes.
This approach is not a universal replacement for WordPress. It requires working with a code repository, a build and publishing process, and the conventions of a Blazor application. Those are reasonable trade-offs for me because they match the tools I use and give me control over the content and site. They may not be the right trade-offs for someone who wants a visual editor or a managed publishing platform.
Common Questions and Answers
Did Blazorade.StaticPages perform the WordPress content migration?
No. The library does not migrate WordPress content. Its purpose is to generate static HTML for pages defined in the Blazor application so that bots and crawlers can discover the content without rendering the application in a browser. I handled the WordPress migration separately with the REST API, GitHub Copilot, and a PowerShell workflow.
Is this approach specific to Blazor WebAssembly?
The publishing workflow described here is specific to my blog, but Blazorade.StaticPages can be used by any Blazor WebAssembly application that needs to generate static HTML. The underlying crawlability problem is common to single-page application technologies that depend on client-side rendering, but other frameworks need their own static rendering or pre-rendering solutions. The goal is the same: make important content available without assuming that every client executes the application.
Is WordPress still a good choice for a blog?
Absolutely. WordPress is the world's most popular CMS for good reasons, and I am not trying to speak badly of it. It remains a capable choice, especially when a visual editor, a large plugin ecosystem, and managed content administration are important. I migrated because I wanted my blog to run on a platform that I feel more comfortable with. The Markdown-based authoring approach became part of the solution when I started the migration work, rather than being the original reason for moving.
I am also proud to say that I am a Blazor fanboy, as you can probably tell from my Blazor and Blazorade articles. The approach described here is not a general-purpose way to move away from WordPress. It is the solution I chose for my own priorities and technical interests. The best choice depends on what a particular site and its author need, not on one platform being universally better.
Summary
I migrated my blog from WordPress to Blazor WebAssembly because I wanted a development and authoring workflow that fit better with the way I work. I write articles in Markdown, use a repeatable PowerShell workflow to migrate existing content, and publish the articles as Razor pages.
Blazorade.StaticPages generates static HTML for those pages, which addresses the crawler visibility challenge while preserving the interactive Blazor application. Azure Static Web Apps provides a suitable hosting model, and the blog is currently running for free within the applicable free quota, subject to Azure's current terms, quotas, and pricing.