What makes Blazor SPA and how does Server-Side Rendering works with Blazor's new Web App

Blazor is often referred as a Single Page Application (SPA) framework. When I first started working with Blazor, I was puzzled about what SPA meant, how components contribute to the SPA architecture, and how all this ties together with interactivity.

Today, I'll address three questions that are likely on everyone's mind:

  1. What is an SPA?
  2. Understanding all new "Blazor Web App" template.
  3. What makes Blazor a SPA application?

What is an SPA?

Image 1 shows how traditional web application processes user requests, it typically involves the user making a request to the server for each new page.

For example, a request for a homepage like "www.facebook.com/home.html" is sent to the server, which is processed by server and then it returns the homepage. And when the user navigates to a profile page, another request is made for "www.facebook.com/profile.html", and the server now sends back the profile page.

Each interaction results in a full page reload.

Image 1: Traditional Web App Architecture

Now let's see how SPA applications works,

The user experience remains unchanged. However, on the server, instead of serving complete webpages, we use components. This is because Blazor is a component-based single-page application.

You might wonder, which single page? In the architecture shown in image 2, App.razor is the webpage where all user requests are directed. The server loads this single page and replaces it with the requested component.

For instance, when a user asks for the home page, App.razor loads the Home.razor component. Similarly, when a user requests the profile page, the server first loads App.razor, which then it loads Profile.razor.

As you can see below, the Profile component has additional components, UserInfo.razor and UserOrders.razor. This is known as component hierarchy.
 

Image 2: Blazor SPA Component-Based Architecture

In contrast, a SPA loads a single <HTML> page and dynamically updates content as the user interacts with the app. This is achieved through dynamic loading of components without requiring a full reload, leading to a smoother user experience.

For example, on YouTube, when you add a comment to a video, the comment section updates without interrupting the video playback. This is because the comment section is a separate component that refreshes independently, leaving other parts untouched.

Pro tip: Blazor has a lifecycle for each component. I've explained this in detail in this article: [https://www.c-sharpcorner.com/article/blazor-life-cycle-events-oversimplified].

Creating a Blazor Web App

Let's see this in action by creating a Blazor Web App.

  • Open Visual Studio and select the Blazor Web App template.

Image 3: Creating a New Blazor Web App

  • Name your app and choose a location.

Image 4: Project Configuration in Blazor

  • Select ".NET 8" as the framework. Set "Authentication type" to "None" and choose "None" for the "Interactive render mode" to ensure we are using Blazor SSR (Static Server-Side Rendering). Ignore the "Interactivity location option" since it does not apply when "None" is selected for the Interactive render mode. Finally, hit the create button.

Image 5: Additional Information for Blazor Web App

    In your Solution Explorer, you will see a structure similar to this:

Image 6: Blazor Web App Folder Structure

Understanding the Code

First, let me show you the sequence of how your requested page loads in a Blazor app. Pay attention to the numbers as they represent the flow of control. We will cover each file in detail below, explaining their role in the process.

Image 7: Control Flow in a Blazor Web App

Let's revisit those early C# days. As we all understand, Program.cs serves as the entry point for C# applications, and in the case of Blazor, it follows the same principle

The following is the code in Program.cs

using MyFirstBlazorWebApp.Components;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorComponents();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles();
app.UseAntiforgery();

app.MapRazorComponents<App>();

app.Run();

Code snippet 1: Program.cs

This file handles HTTP requests made by users (e.g., /index, /profile) as shown in the architecture diagram above.

From lines 18 to 21, you can see the methods dedicated to managing these HTTP requests. On line 23, the HTTP requests are mapped to a component named App.

What is this App? You may ask.

This is the same App.razor shown in our architectural diagram (Image 2), meaning all requests flow through Program.cs before reaching App.razor.

Let's explore what we have in App.razor

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <base href="/" />
    <link rel="stylesheet" href="bootstrap/bootstrap.min.css" />
    <link rel="stylesheet" href="app.css" />
    <link rel="stylesheet" href="MyFirstBlazorWebApp.styles.css" />
    <link rel="icon" type="image/png" href="favicon.png" />
    <HeadOutlet />
</head>

<body>
    <Routes />
    <script src="_framework/blazor.web.js"></script>
</body>

</html>

Code snippet 2: App.razor

This is mostly standard HTML with one notable difference: at line 16, it calls component named <Routes>.

The <Routes> component's job is to locate and load specific requested component. For example, when a user requests the home page, it finds Home.razor, and when they request the profile page, it finds Profile.razor and replaces it.

So what does the <Routes> component contains?

<Router AppAssembly="typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
        <FocusOnNavigate RouteData="routeData" Selector="h1" />
    </Found>
</Router>

Code snippet 3: Routes.razor

It's the Router component responsible for locating the requested component and applying it to the MainLayout at line 3. After routing is completed, the Router component updates the MainLayout.

let's open up MainLayout.razor

@inherits LayoutComponentBase

<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>

    <main>
        <div class="top-row px-4">
            <a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a>
        </div>

        <article class="content px-4">
            @Body
        </article>
    </main>
</div>

Code snippet 4: MainLayout.razor

Notice line 14, where there's a "@Body" placeholder. This is where the requested component is inserted. So, when "Home.razor" is requested, its HTML content will replace the "@Body" placeholder, and the entire page will be sent back through the router to be displayed in the browser.

Tip: The rest of the code represents the layout of your web app. If you want to define a custom layout for your app with a sidebar, header, footer, and dynamic center content, this is the place to do it.

Don't worry too much about all this theory; let's proceed with running the application to see it in action to make sense.

Image 8: Loading a Blazor App

Here you can see the sidebar on the left, from snippet 4 of MainLayout.razor, and the main body content loaded in the center. When the web app runs for the first time, it redirects to the root URL (`/`). In our demo app, this is represented by Home.razor, as indicated in image 7, item 5.

This is how it looks:

@page "/"

<PageTitle>Home</PageTitle>

<h1>Hello, world!</h1>

Welcome to your new app.

Code snippet 5: Home.razor

Now, let's change the user request and click on the Weather button in the sidebar. You'll notice the URL changes to "https://localhost:7106/weather".

At this point, App.razor will ask the Router to find a component with the "@page "/weather" route and replace it in the body section. We have a Weather.razor component with some static data for this purpose.

@page "/weather"
@attribute [StreamRendering]

<PageTitle>Weather</PageTitle>

<h1>Weather</h1>

<p>This component demonstrates showing data.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
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 WeatherForecast[]? forecasts;

    protected override async Task OnInitializedAsync()
    {
        // Simulate asynchronous loading to demonstrate streaming rendering
        await Task.Delay(500);

        var startDate = DateOnly.FromDateTime(DateTime.Now);
        var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
        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();
    }

    private class WeatherForecast
    {
        public DateOnly Date { get; set; }
        public int TemperatureC { get; set; }
        public string? Summary { get; set; }
        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    }
}

Code snippet 6: Weather.razor

As you can see, this Weather.razor component is now placed in the center, having replaced the Home.razor component.

Image 9: Weather Page Loading

Let me inspect the element and show you how it appears as a single page in the browser.

Image 10: Inspecting Elements on the Weather Page

In the center is the code from Weather.razor, and just above that are a couple of <div>, One with class "page" and an inner one with class "sidebar". These are from MainLayout.razor, lines 3 and 4. So, in the end, all these components come together to form a single page.

Take a look at the following GIF. Notice how clicking on Home and Weather page only replaces the body.

GIF 1: Navigating Between Home and Weather Pages

 

And folks, all that talk about SSR (Server-Side Rendering) boils down to this. It's a Blazor hosting model where the application runs on the server, and UI updates are sent to the client via a SignalR connection. This is exactly what we've seen in this article: each time a user requests a webpage, it's generated on the server and sent to the client, then rendered in the browser.

Conclusion

Blazor offers a robust framework for building Single Page Applications using a component-based architecture. You can create highly interactive web applications. The key takeaway is understanding how Blazor handles user requests and dynamically updates content without full page reloads, providing smooth user experience.

Now, go ahead and experiment by creating your own Blazor Web App.


Similar Articles