When it comes to modern web development, performance and user experience are at the forefront of every developer’s mind. With .NET 8 introducing various rendering modes to Blazor, developers will be armed with an array of choices. Among these, server-side rendering and streaming rendering stand out, primarily due to their efficacy in delivering optimized web experiences.

In this post, we’ll delve deeper into these two modes and explore their significance in the new Blazor ecosystem of .NET 8.

Server-side Rendering: A Classic Powerhouse

In part 1 of this series I gave an overview of what server-side rendering (SSR) is, but now it’s time to wade in a little deeper and explore this render mode in more detail.

With .NET 8 comes a new template for Blazor applications simply called Blazor Web App, and by default all components use server-side rendering. This is worth mentioning as you can think of the various render modes as progressive enhancements to each other, with server-side rendering as the base level and auto mode the most advanced.

Server-side rendered page components in Blazor are going to produce the same experience as Razor Pages or MVC apps. Each page is going to be processed and rendered on the web server, once all operations to collect data and execute logic have completed, then the HTML produced will be sent to the browser to be rendered.

render-modes-ssr

There is no interactivity in this mode making applications very fast to load and render. This makes it an excellent choice for apps that deal with a lot of static data. I’m sure many line of business applications would fall into this category as well as online shopping apps where rendering speed is key.

Another boon of this mode is it’s ability to allow excellent search engine optimisation (SEO). Applications just produce regular HTML, so there will be no issues with web crawlers indexing pages, as can be the case when using single page applications (SPA).

But you might be wondering why would I choose to do that with Blazor when I could already use Razor Pages or MVC? You wouldn’t be wrong for asking, here are a couple of reasons why.

First, Razor Pages and MVC don’t offer very good options for building reusable components. Blazor on the other hand, has an excellent component model.

Second, when choosing either Razor Pages or MVC you are locked into a server-side rendering application. If you want to add any client-side interactivity, you either have to resort to JavaScript, or you could do it by adding in Blazor components. If you choose the latter, then why not just use Blazor for everything?

We’ve covered a lot of theory so far, let’s put those learnings into action by creating and running a Blazor app using the new SSR mode.

Configuring server-side rendering

As previously mentioned, there is a new template for Blazor applications in .NET 8. This template removes the Blazor WebAssembly and Blazor server specifics and creates a standard starting point using SSR. To create a new Blazor app using this template we can use the following command via the .NET CLI.

dotnet new blazor -n BlazorSSR

That’s it! We now have an application ready to go using server-side rendering. Remember, SSR is now the default mode for new Blazor applications and components. The other render modes are enhancements on top of it.

The key bits of configuration that make this work are contained in the Program.cs file.

using BlazorSSR;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorComponents(); // 👈 Adds services required to server-side render components

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.MapRazorComponents<App>(); // 👈 Discovers routable components and sets them up as endpoints
app.Run();

The AddRazoreComponents method will register the services needed to render server-side components in the application. The other bit of configuration is the MapRazorComponents<T> middleware. This takes in a root component that’s used to identify the assembly to scan for routable components. For each routable component found, an endpoint is setup. Yes, you read that correctly. Each routable component is represented as an endpoint and this middleware generates those endpoints on application start up. Under the hood, this uses the minimal API technology and a new result type called RazorComponentResult. You can also setup your own endpoints to return the result of executing arbitrary Blazor components.

app.MapGet("/greeting", () => new RazorComponentResult<GreetingComponent>());

But that’s something we’ll delve deeper into in another post.

The other notable change in the new template is that there is no longer an index.html or _Host.cshtml page. The page markup that would normally be found in those pages is now all contained in the App.razor component along with the Router component. Previously, Blazor Server apps, or Blazor WebAssembly apps with pre-rendering enabled, would need to be hosted in a Razor Page as we couldn’t route directly to a Blazor component. But with the new endpoint approach we just saw, that is no longer the case.

If you look at the App.razor component you will notice that there is a JS file referenced at the bottom.

<script src="_framework/blazor.web.js" suppress-error="BL9992"></script>

This is not needed for SSR. By default, the template can do streaming rendering as well and that’s why the script is referenced. If you only want to use SSR then it can be safely deleted.

Running the app

Let’s see what this looks like when we run the app. Before we do, we’re going to make one other quick adjustment. The Weather.razor component is configured to use streaming rendering by default. Right now, we’re just focusing on SSR, so we’re going to delete the reference to the streaming rendering attribute at the top of the page.

@attribute [StreamRendering(true)]

We’re now ready to run that application.

ssr-page1

At this point, things don’t look much different to a normal Blazor application. The key thing to spot is that there is no JavaScript being downloaded in this case. It’s just HTML and CSS files.

Let’s navigate to the Weather page and see what happens…

ssr-page2

When we navigated there was a full page refresh. As you can see from the dev tools, all of the assets have been downloaded again, along with the new page we’ve navigated to. There has been no client-side navigation or routing involved, just classic request and response.

By the way, if you’re curious about the load time and haven’t looked at the code, the Weather page has a built-in 1 second delay to simulate getting data from a database. Something we’ll leverage when looking at streaming rendering next.

Streaming Rendering: A Modern Marvel

As previously mentioned, render modes can be thought of as progressive enhancements over each other, the base mode being SSR. The next layer is streaming rendering.

Streaming rendering is a small enhancement over SSR. It allows pages that need to execute long running requests to load quickly via an initial payload of HTML. That HTML can contains placeholders for the areas to be filled with the results of the long running call. Once that long running call completes, the remaining HTML is streamed to the browser and is seamlessly patched into the existing DOM on the client. The cool thing here is that this is all done in the same response. There are no additional calls involved.

render-modes-streaming-rendering

Streaming rendering is going to have the same types of use cases as SSR does. But really any application that is happy to be server-rendered overall, but might need a little help to improve loading times, is a good candidate.

What about caching? Can’t we use that to solve the long running call issue on the server? Why do we need a new render mode?

Fair questions, the answer here is that not all long running calls can be cached. For example, a page might need to load live data from an external API, such as stocks or currency exchange rates. It’s also perfectly reasonable that live data needs to be loaded from the apps database. In these cases, streaming rendering offers a much nicer experience for the user compared to waiting on a blank screen for a few seconds.

Configuring streaming rendering

In order to configure streaming rendering, we need to have the same basic configuration used for SSR. The two additional things required are the inclusion of the Blazor Web javascript file in App.razor.

<script src="_framework/blazor.web.js" suppress-error="BL9992"></script>

The other is the addition of the streaming rendering attribute on any page that we want to be streaming rendered.

@attribute [StreamRendering(true)]

If you are keen to do as little typing as possible, the attribute is defaulted to true, meaning that just the presence of it is enough to enable streaming rendering. So the following is also valid:

@attribute [StreamRendering]

As previously mentioned, these are both included by default in the new Blazor template with .NET 8. We just removed them when we were looking at SSR so we could see exactly how that mode behaved in isolation. So if you’re following along, you can just add those two bits back into your existing project and you’ll be good to go.

Running the app

When running a streaming rendered app, the initial load isn’t much different to that of an SSR one. The key difference is the inclusion of the blazor.web.js file.

streaming-page1

The differences start to show when navigating around. If we navigate to the Weather page as we did before, we’ll see a couple of interesting changes.

The first is that we now see some placeholder text when that page first renders.

streaming-page2a

This wasn’t something we saw before when using SSR. If we take a look at the code for the page we can see where this comes from.

@page "/weather"
@attribute [StreamRendering(true)]

<PageTitle>Weather</PageTitle>

<h1>Weather</h1>

<p>This component demonstrates showing data from the server.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p> @* 👈 Placeholder while data is loaded *@
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th>Temp. (C)</th>
                <th>Temp. (F)</th>
                <th>Summary</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private static readonly string[] Summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    private WeatherForecast[]? forecasts;

    protected override async Task OnInitializedAsync()
    {
        // Simulate retrieving the data asynchronously.
        await Task.Delay(1000);

        var startDate = DateOnly.FromDateTime(DateTime.Now);
        forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = startDate.AddDays(index),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = Summaries[Random.Shared.Next(Summaries.Length)]
        }).ToArray();
    }
}

If forecasts is null then the page renders the loading markup. We can in the OnInitializedAsync method that forecasts gets populated after a simulated 1 seconds delay. Once this happens, the else condition of the if statement is triggered. The table containing the forecasts is generated and streamed to the client where it’s patched into the existing DOM.

streaming-page2b

The second interesting change can only be seen when checking the dev tools. When navigating to the Weather page we didn’t do a full page refresh, it was actually a fetch request handled by Blazor.

streaming-devtools

Looking at the network tab, we can clearly see the request for the index page and the site assets (JS/CSS) are still present, even though Preserve log is disabled. We can also see that the request for the weather URL is of type fetch and was initiated by the blazor.web.js script. So what’s going on here?

This is Blazor’s enhanced page navigation in action. Once we include the blazor.web.js script, Blazor can intercept our page requests and apply the response to the existing DOM, keeping as much of what exists as possible. This mimics the routing experience of a SPA, resulting in a much smoother page load experience for the user, even though the page is being rendered on the server.

This enhanced navigation also works with a purely SSR application as well. It’s not tied to streaming rendering in any way. You just need to include the blazor.web.js script.

Summary

In this post, we’ve explored two of the new rendering modes coming to Blazor in .NET 8: Server-side rendering, and Streaming rendering.

Server-side rendering, or SSR, is the new default for Blazor applications and components going forward. Using the classic approach of processing all logic on the server and producing static HTML that is sent to the client. This render mode is excellent for applications that need no client side logic. They just need to render pages quickly and efficiently.

Streaming rendering can be thought of as an SSR+ mode. If your application has to make long running calls when constructing a page, and those calls don’t work well with caching approaches, then streaming rendering is going to be a great option. It will send the initial HTML for the page down to the browser to be rendered just as quickly as an SSR page. However, where the content from the long running call should be, placeholders will be rendered instead. Once the long running call has completed, the remaining HTML will be streamed down to the browser over the existing response where it will be seamlessly patched into the DOM.

We also saw a perk of including the blazor.web.js script–enhanced navigation. This allows Blazor to provide a SPA style page navigation experience, even though pages are being rendered on the server.

What do you think to these two new render modes? Got any ideas what sort of applications you’re going to use them for? I’d love to know your thoughts, leave a comment below.