DEV Community

loading...
Cover image for Render a Blazor component to HTML string for Blazor WebAssembly app's search engine optimization that is hosted on GitHub Pages

Render a Blazor component to HTML string for Blazor WebAssembly app's search engine optimization that is hosted on GitHub Pages

jsakamoto
Microsoft MVP for Visual Studio and Development Tech. (prefer C#, .NET Core, ASP.NET Core, Azure Web Apps, TypeScript, and Blazor WebAssembly App!)
・3 min read

Introduction

Usually, the internet search result of Blazor WebAssembly is not good.

In many cases, the result is just "Loading...".

"Loading..."

Fortunately, we can avoid this by doing server-side prerendering.

Aside: I heard in some rumors that the Google search crawler process WebAssembly process such as a Blazor WebAssembly app. But I don't have any information sources, any evidence.

Pre requirement, we have to run the ASP.NET Core process at the server side to implement server-side prerendering even if the hosting model of the Blazor app is WebAssembly.

But if this limitation is no problem, you can implement the server-side prerendering very easily.

What you have to do is just following steps.

  1. Create a Razor page (.cshtml file) for prerendering,
  2. Specify that Razor page as the fallback page,
  3. Write a <component .../> tag that is a special tag of ASP.NET Core tag helpers, with RenderMode="Static" property specified.

Yes, we may have to consider some implementation issue, because the code will run at the server process during server-side prerendering.

But anyway, basic implementation to server-side prerendering is very simple as above.

static contents only hosting environment

However, if we can't use the ASP.NET Core server process such as the Blazor WebAssmebly app was deployed to the GitHub Pages, what should I do?

Fortunately, we have one more chance to prerendering.

After the publishing process, we can inject prerendered HTML fragments into the index.html that is published as a post-process of the publishing.

I came up with some ideas to get the prerendered HTML strings of a Blazor component.

One of the ideas is, launch an ASP.NET Core host temporary for prerendering, and send HTTP GET request to get prerendered HTML contents.

I have never tried this method but it will work well I think.

However this method might be complex to implement, and it may require some concerns such as finding a free TCP port for ASP.NET Core host.

Another way, I focused on the <component .../> ASP.NET Core tag helper.

I guessed I can get a prerendered HTML string from the output of the <component .../> ASP.NET Core tag helper, and this way might be more easily rather than the way that launching whole of the ASP.NET Core host.

So I tried it, and... it was succeeded!

Render a Blazor component to HTML strings by <component .../> ASP.NET Core tag helper

The implementation of the <component .../> ASP.NET Core tag helper is ComponentTagHelper class that lives Microsoft.AspNetCore.Mvc.TagHelpers name space.

To render a Blazor component to HTML strings by using ComponentTagHelper class, I have to set up some objects that the ComponentTagHelper is dependent on.

The list of the classes/interfaces of those dependent objects is here:

  • ServiceCollection
  • IServiceProvider (that was added Logger and Razor Pages services)
  • IServiceScope
  • FeatureCollection
  • IHttpRequestFeature
  • HttpResponseFeature
  • DefaultHttpContextFactory
  • HttpContext
  • TagHelperAttributeList
  • TagHelperContext
  • TagHelperOutput
  • DefaultTagHelperContent

The code to setup those objects became around 35 lines.

The coding to set up those dependent objects above was not so hard work.

It was not required using undocumented API, and not required any deep hacks such as using "Reflection" technology.

After setup those objects (HttpContext, TagHelperContext, TagHelperOutput, etc.), I can create instance of ComponentTagHelper class, like this code.

var componentTagHelper = new ComponentTagHelper
{
    // 👇 Specify the Blazor component class to render to HTML strings here.
    ComponentType = typeof(App),
    RenderMode = RenderMode.Static,
    ViewContext = new ViewContext { HttpContext = httpContext }
};

After creating the ComponentTagHelper object, invoke ProcessAsync() method.

await componentTagHelper.ProcessAsync(tagHelperContext, tagHelperOutput);

After invoked ProcessAsync() method, the TagHelperOutput object that was passed as one of the arguments of ProcessAsync() method contains the result of rendered the Blazor component to HTML fragments.

Finally, I can get the HTML string from that TagHelperOutput object.

var content = tagHelperOutput.Content;
var stringWriter = new StringWriter();
content.WriteTo(stringWriter, HtmlEncoder.Default);

// 👇 This is the result of rendering the Blazor component to HTML strings.
var html = stringWriter.ToString();

By this implementation, I could inject prerendered contents into index.html as a post-process of publishing!

Conclusion

You can see all of the implementations of this article at the source code of my "CUI Flavored Portfolio Site" Blazor WebAssembly app that is deployed at the GitHub Pages hosting.

The source code is on my GitHub repository.

Please notice that the way this article described could not prerender outside of the Blazor component such as <title> and <meta name="ogp:..."...>.

If you have to prerender those elements, yes you can do it, but you may have to more additional implementations, or may have to choose another technic, I think.

Anyway, we could improve the internet search result of the Blazor WebAssembly app hosted on GitHub Pages by using this technic!

Good search result!

Happy coding! :)

Discussion (1)

Collapse
ondrejuzovic profile image
OndrejUzovic

I like the solution you provided. However it seems the code example is missing at the provided github link.
Please, would it be possible to provide those 35 lines of the setup code you mention in the article?

I would really like to know how you instantiated HttpContext, tagHelperContext and tagHelperOutput.