Prerendering your Blazor WASM application with .NET 5 (part 1)

October 8, 2020 · 7 minute read · Tags: blazor | prerendering

If, like me, you’re old enough, you probably remember all those elaborate animations that used to blight the web.

Back when Flash ruled the world you would frequently land on a web page only to be shown an interminable, unskippable animation which would actively stop you from proceeding until it was finished.

But wait! Surely those days are behind us? Haven’t we long since moved away from forcing our users to wait their turn?

Loading spinners anyone?

With so many applications running in the client these days (think javascript SPAs), we still face delays, often forced to wait while assets and/or data loads.

This is very much the case if you use Blazor WASM for your web app.

When users visit your Blazor WASM application for the first time their browser downloads a version of the .NET runtime, plus all your site’s assets (in the form of dlls).

The good news is they only need to do this once because after that the browser has them cached, but this can still leave your users twiddling their thumbs while they wait for the download to complete.

Even then, after they’ve completed the initial download, every time they access your site they’ll encounter a brief “loading” phase while everything spins up.

Prerendering to the rescue

Once possible solution is to prerender your app.

It’s been possible for a while now to prerender your Blazor applications but the overall experience has been updated and improved with .NET 5.

This article has been updated to reflect a couple of of small changes introduced in .NET 5 RC2

Unleash Blazor's Potential

Blazor promises to make it much easier (and faster) to build modern, responsive web applications, using the tools you already know and understand.

Subscribe to my Practical ASP.NET Blazor newsletter and get instant access to the vault.

In there you'll find step-by-step tutorials, source code and videos to help you get up and running using Blazor's component model.

I respect your email privacy. Unsubscribe with one click.

    The rest of this article builds on the really useful information put out there previously by Chris Sainty and Daniel Roth…

    Dan has a very handy looking BlazorNet5Samples repo on Github which is well worth checking out and includes an example of prerendering a Blazor WASM application.

    Typically when you interact with a Blazor WASM app the browser first receives an HTML file (index.html) back from the server, which then takes care of fetching everything else your app needs to run, before spinning up your app via web assembly.

    Once the initial request has completed, interacting with your app is generally pretty quick as subsequent requests are all handled right there in the browser (with Blazor figuring out what to render or rerender).

    However, during that initial load your users will be left with a loading indicator while they wait for the process to complete.

    Prerendering flips this initial request loading around so that it takes place on the server and static HTML is returned to the browser.

    This makes for a really snappy first load.

    The browser can then download the .NET runtime (if it doesn’t already have it) and your application’s dlls in the background. By the time your users try to interact with the app the chances are it will have finished and everything will have sprung into life!

    Prerender your .NET 5 WASM application - step by step

    The easiest way to start is by creating a new Blazor WASM project that’s hosted via ASP.NET core.

    md ExampleApp
    cd ExampleApp
    dotnet new blazorwasm --hosted
    

    When you do this you get three projects:

    • ExampleApp.Client
    • ExampleApp.Server
    • ExampleApp.Shared

    The Blazor WASM components are all defined in the .Client project.

    When you launch the .Server project it serves that client project to the browser.

    To prerender the client application you’ll want to start by ditching the index.html file from .Client/wwwroot.

    Instead of that we’re going to use a .cshtml file in the .Server project to load the app.

    Create _Host.cshtml

    Create a file called _Host.cshtml in the Pages folder in the Server project.

    Here’s a minimal example…

    Server\Pages\_Host.cshtml

    @page
    @using ExampleApp.Client
    
    <!DOCTYPE html>
    <html>
    
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" 
            content="width=device-width, initial-scale=1.0, 
                     maximum-scale=1.0, user-scalable=no" />
        <title>Your Site</title>
        <base href="/" />
        <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
        <link href="css/app.css" rel="stylesheet" />
        <link href="ExampleApp.Client.styles.css" rel="stylesheet" />
    </head>
    
    <body>
    
    <component type="typeof(App)" render-mode="WebAssemblyPrerendered" />
    
    <script src="_framework/blazor.webassembly.js"></script>
    </body>
    
    </html>
    

    There’s a couple of things to note here.

    I’ve added a reference to the main App component, and set its render-mode to WebAssemblyPrerendered. This is the new .NET 5 secret sauce that makes sure the component (App in this case) will be rendered as static HTML and then become ‘dynamic’ once the browser finishes loading Blazor.

    Beyond that I’ve also included the blazor.webassembly.js script. Without this our component(s) would never become interactive.

    Finally, I’ve referenced the client app’s stylesheet. This is auto-generated as part of Blazor’s support for CSS Isolation.

    You’ll need to make sure you reference your .Client project from the .Server project.

    Add a fallback endpoint pointing to _Host.cshtml

    Now we need to make sure all requests to the app are forwarded to _Host.cshtml if a more specific route isn’t available.

    Head over to Startup.cs and update the line which sets index.html as the fallback file to point to our Host page instead:

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

    Now when you first attempt to navigate to a page in the application the request will be directed to the new _Host.cshtml page which will, in turn, prerender the relevant component(s) and return static HTML back to the browser.

    Enable Tag Helpers

    If you run this in the browser however you might find yourself staring at a blank screen.

    If you inspect the source code in the browser you’ll see the raw component element.

    This is because that <component /> element is actually an ASP.NET tag helper.

    You can find out more about ASP.NET Tag Helpers in the official docs.

    Tag helpers are able to employ the server to do various clever things before modifying the markup which gets returned to the browser.

    In this case, the component tag helper should do all the hard work of rendering HTML for our Blazor WASM app but at the moment it doesn’t appear to be doing very much at all!

    To make it work add a _ViewImports.cshtml file to your Server application’s Pages folder as follows:

    Server\Pages\_ViewImports.cshtml

    @using ExampleApp.Server
    @namespace ExampleApp.Server.Pages
    @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
    

    Run this again now and hopefully you’ll find yourself looking at the oh so familiar Blazor project template.

    What’s more, you should find as you interact with the app (for example by incrementing the count in the Counter component) everything works as it normally would, including clicking links to move between pages.

    Success? Not so fast!

    So it works, but before you go off to celebrate, check your browser console and you’ll discover a big red error.

    Microsoft.JSInterop.JSException: Could not find any element matching selector ‘#app’.

    So what gives?

    Well your Blazor client application is expecting to load into an element with the #app id.

    Essentially our use of the <component> tag helper superceeds this but we forget to tell that to the .Client project!

    Not to worry you can easily fix it by heading over to the Client project’s Program.cs.

    In there, remove this line:

    Client\Program.cs

    builder.RootComponents.Add<App>("#app");
    

    With that line removed the Blazor WASM client app will no longer attempt to load itself into an #app element, leaving the component tag helper to do its thing.

    Eek, don’t refresh that page

    At this point you might think everything is working perfectly.

    You can navigate between different pages in your application, click on buttons and everything works!

    However, if you navigate to the FetchData page then hit refresh, you get an error!

    InvalidOperationException: Cannot provide a value for property ‘Http’ on type ‘ExampleApp.Client.Pages.FetchData’. There is no registered service of type ‘System.Net.Http.HttpClient’.

    My next post will explore this issue, explain what’s going wrong and offer a potential solution to the problem.

    In summary

    If you’re able to host your Blazor WASM projects via ASP.NET you can take advantage of prerendering improvements in .NET 5.

    With a few tweaks you can render your app as static HTML so it loads nice and quickly in the user’s browser.

    Once Blazor WASM has kicked in your app will become interactive and everything works just as if you’d loaded the Blazor app without prerendering.

    Further reading

    Join the Practical ASP.NET Newsletter

    Ship better Blazor apps, faster. One practical tip every Tuesday.

    I respect your email privacy. Unsubscribe with one click.

      Next up

      3 simple design tips to improve your Web UI
      Spruce up your features
      The quickest way to integrate PayPal checkout with Blazor SSR in .NET 8
      JavaScript Interop works differently with Blazor Server-side rendering
      Interactive what now? Deciphering Blazor’s web app project template options
      Create a new Blazor Web App and you’ll be asked how you want interactivity to work, but what does it all mean?