9/27/2023

Blogging with Blazor & Cloudflare

Blazor is a dotnet/ C# based web app framework. It promises that you'll be able to "build full stack web apps without writing a line of JavaScript." At time of writing, this post is hosted on a Blazor frontend application. While building the blogging engine, I discovered that Blazor is cool, it runs on Cloudflare Pages, it's a good dev experience, but it's no substitute for JavaScript (yet).

Getting started

I decided to use a Blazor WASM project for this site, because I wanted to run it on Cloudflare pages. Pages are awesome because they run on Cloudflare's edge network, AND they come for free. Unfortunately, while Cloudflare does offer a "server-side" environment (called Cloudflare Workers), they only support a Javascript runtime. For full-stack Blazor (including server-side rendering) a different hosting service might be required.

I created this app using dotnet 7:


# check dotnet version
dotnet --version
7.0.401

dotnet new blazorwasm -o edge
...

Out of the box, this creates a Blazor WASM project with some example components.

Rendering markdown

Since I was porting several existing posts, I wanted to keep using markdown as a simple storage format for all my writing. Therefore, I needed a markdown to HTML renderer. I'd usually use a pre-built library in this situation, and I quickly discovered that there are many more Javascript implementations than dotnet. Furthermore, WASM applicatons cannot manipulate the DOM (source), which makes client side markdown rendering essentially impossible using only C#.

I decided to expose the raw markdown via GET request, and then render to HTML using Showdown JS and Blazor's JS Interop functionality.

// Post.razor.js
export async function setInnerHtmlFromRequest(id, url) {
  const converter = new showdown.Converter(); 
  const content = await fetch(url);
  const text = await content.text();
  const html = converter.makeHtml(text);
  document.getElementById(id).innerHTML = html;
}
// Post.razor
// other implementation omitted
private async Task LoadPost()
{
    var path = $"/posts/{PostId}.md";
    // reference a Javascript module from Blazor
    var module = await JS.InvokeAsync<IJSObjectReference>("import", "./Pages/Post.razor.js");
    // call a function in the module
    await module.InvokeVoidAsync("setInnerHtmlFromRequest", ContentId, path);
}

While this works, it has several downsides:

  • Lack of type safety on Blazor/JS interop calls and responses.
  • I now need to support Javascript in the project
  • Small performance hit on interop call
  • Harder to migrate to server-side rendering

Running on Cloudflare Pages

Cloudflare have some good documentation on how to deploy a Blazor site using Pages. It's quite simple, but the downsides are:

  • cloudflare build agent doesn't have dotnet installed, which increases the build time. My simple project takes ~10 minutes to build on Cloudflare.
  • Cloudflare doesn't support server-side C# and so there's no server side rendering
  • No server-side rendering also means no fallback for browsers that don't support WASM.

What I get for free with Cloudflare is:

So as with all engineering challenges, building a blog site with Blazor WASM and Cloudflare has pro's and con's. It's probably better to just statically render your blogs at build time, but where's the fun in that?

Check out the code here