Server Side Render Blazor Client Side App (WASM)

Steve Peirce
4 min readMay 24, 2020

--

NOTE: This article is now out of date. From Net6 Blazor supports PreRendering whilst preserving state.
ASP.NET Core updates in .NET 6 Preview 2 | ASP.NET Blog (microsoft.com)

TL;DR; Example of Blazor Client app with SSR is here: https://github.com/srpeirce/Blazor-SSR

Background

I spend most of my time developing in C# and .Net core. I’ve been keeping an eye on Blazor since the original experiments took place, but I’ve been waiting for the Client-Side (WebAssembly) version be released.

Blazor is the new framework in .Net Core for writing client side applications. I’m interested in the client model that uses WebAssembly to allow Single Page Applications written in C# to work in the browser (goodbye js ;P).

Starting off

I installed the latest version of dotnet core, in my case 3.1.300 — this ensures you have the latest dotnet new templates.

I created a new Blazor WASM project using the dotnet templates.

dotnet new blazorwasm -ho -p -n Blender.Web`

The flag -ho creates a Server project to host the client side app.

The flag -p configures the application to be a SPA.

This creates a solution file with 3 projects,

Projects

Running the app

I ran the Blender.Web.Server to test the application. Everything is great, it runs…oh wait, except for those few seconds to load :(

Loading Blazor WASM

Okay this is a Debug build, so I tried it passing in the Release flag and the loading was still noticeable.

I actually pushed the app to the cloud and loaded it from my Galaxy Note 8 — it took ~4 seconds to load the app.

This isn’t great — I’m not sure if this will be good for Production :-(

Lighthouse report — Notice the rendering screenshots :(
Notice the rendering screenshots from Lighthouse :( Note: This is running Debug configuration.

Don’t give up

Before calling it a day I thought I would try SSR. This is where the server renders the webpage on the server and sends the HTML to the browser. Then from that point the client side app takes over.

I couldn’t find any examples (perhaps my Google Foo was weak last night) so I felt my way through.

Here is what I ended up with….

Adding Server Side Rendering (SSR) / PreRendering

Create a new page:

Create Blender.Web.Server/Pages/_Host.cshtml

Copy the contents from the GitHub here: _Host.cshml

The important parts are that we are choosing the ServerPrerendered render mode.

<app>    
<component type="typeof(App)" render-mode="Static"/></app>

And we are choosing the client js file:

<script src="_framework/blazor.webassembly.js"></script>

I deleted the Index.html as this isn’t needed anymore, this is because _Host.cshtml is now the entry point for out application. I do not intend to run/deploy the webassembly app standalone.

I then updated `Update Blender.Web.Server/Startup.cs` to map the fallback page to the new host page.

app.UseEndpoints(endpoints=>{
endpoints.MapRazorPages();
endpoints.MapControllers();
endpoints.MapFallbackToPage("/_Host");
});

Note: We are not creating any SignalR circuits here, the server will just render the page but will not maintain a connection with the browser.

When we run the server we get Server Side rendering, whoohoo :D No loading page here, after the inital page load then we are running the client application.

Checkout the rendering images. Note: This is running Debug configuration.

All done….but wait what…!?

Navigating to the page

Checking each page of the app, everything is great.

But wait, what happens when we refresh the Fetch Data page:

Boom!
Boom!

What has happened?

Okay we are missing a dependency of HttpClient on the server side. This is because the DependencyInjection configuration for HTTPClient has not been configured on the server application, it is seperate from the client app.

The fix, configure the DependencyInjection on the Server.

I actually extracted a WeatherForecastService for this experiment.

And updated the Blender.Web.Server/Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddRazorPages();
services.AddHttpClient<WeatherForecastService>(options => options.BaseAddress = new Uri("https://localhost:5001/"));
}

And updated the Blender.Web.Client/Program.cs:

builder.Services.AddTransient(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });            builder.Services.AddTransient<WeatherForecastService>();

And viola, everything now works!

The lesson:

When SSR we need to test each page from the Client and from the Server to make sure all of our dependencies are configured correctly.

Oh Yerr, It’s a PWA

We can install the App, whoohoo.

Install the PWA

Some further experimentation is needed, but it looks like speed might be okay…

--

--

Steve Peirce

Principal Engineer at M-KOPA | Co-Founder of Powered4.TV | Member of .NET Foundation