Building a Software Survey using Blazor - Part 4 - Render Mode

I've decided to write a small survey site using Blazor. Part of this is an excuse to learn Blazor.

As I learn with Blazor I will blog about it as a series of articles.

This series of articles is not intended to be a training course for Blazor - rather my thought process as I go through learning to use the product.

Earlier articles in this series:


Your Blazor server side app will be initiated something like this in _Hosts.cshtml:

_Hosts.cshtml


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

I believe by default, the render-mode will be set to ServerPrerendered - and I think most examples will show render-mode with that option - and I have to admit I didn't really pay much attention to it until I struggled with my automated end-to-end tests.

My problem

I had built an end-to-end happy path test using Selenium and orchestrated it to run every hour using Azure DevOps. The test was fairly simple; it ran through every page of the survey with defined responses - then checked against CosmosDB to ensure that the survery results had been recorded.

I could run the test locally and it worked perfectly everytime.

And it would mostly work correctly when being run on the hourly schedule - but it wasn't consistent.

Occassionally I'd get failures. When I investigated it looked as if my Selenuim test simple wasn't pressing the "Next" button on the home page:

home page

I experimented with various techniques to track down the problem - but eventually I discovered the cause.

It was the render-mode.

ServerPrerendered

What I hadn't taken the time to fully appeciate was how the ServerPrerendered worked.

From the Microsoft Docs page:

> "Renders the component into static HTML and includes a marker for a Blazor server-side application. When the user-agent starts, it uses this marker to bootstrap a blazor application."

The important part here is the "Renders the component into static HTML".

On first request, the ServerPrerendered will return the the browser a static HTML representation to the page requested (with all its components). The browser will display this HTML - then run the Blazor Javascript to establish a SignalR connection. Once the SignalR connection is established THEN the Blazor page is re-rendered - this time with all the Blazor goodness.

What you are effectively getting it a two stage render to the browser:

  1. Cosmetic render of the component - all the HTML - but none of the functionality
  2. Functional render of the components - replaces the cosmetic only HTML with all the intended functionality

This is a techniques called hydration that has been popular in Javascript Single-Page-Apps (SPAs) for a while. The intention is to provide the user with valuable content (the HTML) while the app is downloading, initializing and starting in the background. Once ready the app would "take over" - replacing the HTML on screen.

Done right it would look to the user that the app is ready much quicker that it actually is (plus the technique provides a considerable benefit for SEO - the clawler doesn't need to execute the Javascript to understand the content).

Back to my problem

So my end-to-end tests where failing due to recieving the cosmetic-only HTML and attempting to use it BEFORE the app had rehydrated and became functional.

The test would open the website - get the HTML for the first page and click on the Next button.

But this was BEFORE the app had rehydrated - thus there was no click handler behind the Next button. Thus clicking on it did nothing.

Thus when the end-to-end test asserted that the next page had been loaded, it failed because of course none of the logic had been run.

The fix

I could have changed the render-mode (see below) but I chose to implement a loading spinner while in that pre-rehydrated state;

prerender loading page

I felt this was best both for the user and for Selenium. The user wouldn't become frustrated by a button that did nothing and Selenium would wait until the Next button appeared.

For this I created the PreRenderLoadingMessage component:

PreRenderLoadingMessage.razor


@if (HasRendered)
{
    @ChildContent
}
else
{
    <LoadingSpinner Title="Loading ..." />
}


@code {
    [Parameter]
    public RenderFragment ChildContent { get; set; }

    private bool HasRendered { get; set; }

    protected override void OnAfterRender(bool firstRender)
    {
        if (firstRender)
        {
            HasRendered = true;
            StateHasChanged();
        }
    }
}

Then I wrapped the entire app within it:

App.razor


<PreRenderLoadingMessage>
    <Router AppAssembly="@typeof(Program).Assembly">
        <Found Context="routeData">
            <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>Sorry, there's nothing at this address.</p>
                @{ Is404 = true; }
            </LayoutView>
        </NotFound>
    </Router>
</PreRenderLoadingMessage>

Thus the first render (the prerender) shows the LoadingSpinner, then subsequent (hydrated) renders would show the @ChildContent.

This technique was taken from this Microsoft Docs page.

This approach solved my problem nicely. The end-to-end tests worked like a charm once I implemented it.

Other modes

So there are two other modes available to render-mode - Server and Static.

Static will just render static HTML - it doesn't provide any of the functionality. I've not seen a good reason to use this mode - although I do wonder if it could be used with a static site generator - something like GatsbyJs. I suspect its there for future use.

Server will operate the same as ServerPrerendered but without the static HTML version on the first page request. Rather it leaves the app content empty until the SignalR connection is established and Blazor is running fully.

The problem with Server mode was that it can take a few seconds to establish that SignalR connection and to get everything "started". I didn't want to provide an empty page to the user. I'd end up adding some form of loading message in the _host.cshtml - only to then have to remove once the Bazlor app was ready.

Thus it made much more sense to use the functionality baked into Blazor with the ServerPrerendered - and I think that the PreRenderLoadingMessage is a good way to handle that.

About the author:

Mark Taylor is an experience IT Consultant passionate about helping his clients get better ROI from their Software Development.

He has over 20 years Software Development experience - over 15 of those leading teams. He has experience in a wide variety of technologies and holds certification in Microsoft Development and Scrum.

He operates through Red Folder Consultancy Ltd.