Does .NET 6 fix Blazor Prerendering?

April 19, 2021 · 6 minute read · Tags: blazor | prerendering

Nobody wants to see Loading… when they visit your site, but that’s what they’ll get if you use Blazor WASM for your site.

Unless, that is, you employ prerendering.

Ways to avoid loading…

Blazor Server apps load near instantly in the browser, but require a permanent connection to a server (hence the name).

This works well for ‘Line Of Business’ apps but less so for web sites which are to be consumed by the general public (where latency and scaling up can become a problem).

Blazor WASM provides a viable alternative, liberating your app from needing a server (except of course for fetching or persisting data).

But here we run smack bang into one of the big drawbacks of Blazor WASM; your users have to wait a variable amount of time for the application to ’load’ before they can interact with it.

This limitation makes it a sub-optimal choice for many use cases. Take for example a blog. It kind of sucks if your users hit a link to read an article you’ve written only to be faced with a loading spinner while .NET downloads to their browser before they can start reading anything at all.

It also makes things like SEO and social media cards (like those previews you see when a link is shared on Twitter) difficult because every request to your site has to navigate this initial download first.

Which brings us to prerendering.

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.

    It’s possible to configure your Blazor WASM application so that static HTML (generated on the server) is returned in response to the first request made, making for a super snappy first load.

    This buys you some time; while your user’s are happily looking at the content that’s already appeared in front of them, their browser can silently download the rest of the assets required to make the site ‘interactive’ in the browser.

    Sounds great, what’s the catch?

    This sounds like the perfect compromise, so why isn’t everyone doing it?

    One reason is that server prerendering rules out hosting your site using static hosting providers like Netlify (because you need a server to handle that first request).

    But perhaps the biggest limitation with prerendering (before .NET 6) is the way that data is potentially retrieved twice.

    When your users access a page on your site which retrieves data, they will see the page as it was initially rendered on the server, then the page/component will be initialized/rendered again in the browser (when the assets have finished downloading).

    As part of this process the data is retrieved again, often leading to a ‘flash’ as the screen renders the data for a second time.

    .NET 6 addresses this problem and opens the doors to prerendering for many more applications.

    Preserving state between renders

    .NET 6 enables you to persist data which has been used for the first render (on the server), then retrieve and use this data again when the app is rendered in the browser.

    Taking the standard weather data example from the Blazor project template, here’s how it works.

    If you’ve set up your Blazor app for prerendering you’ll probably have a _Host.cshtml page in the Server project.

    Make sure to add a call to persist the component state using <persist-component-state />.

    <body>
    
    <component type="typeof(App)" render-mode="WebAssemblyPrerendered" />
    
    <persist-component-state />
    
    <script src="_framework/blazor.webassembly.js"></script>
    </body>
    

    This needs to be included after the component(s) whose state you wish to persist. In this case we’re prerendering the entire application (App) so it makes sense to include <persist-component-state> directly after the App component is declared.

    Next you’ll need to inject ComponentApplicationState into your component (which is FetchData in this case).

    FetchData.razor

    @inject PersistentComponentState ApplicationState
    

    Now you can use ApplicationState to store and retrieve data.

    The first step is to make sure any data is persisted when the component is prerendered on the server. For that you can hook into the OnPersisting event.

    private PersistingComponentStateSubscription _subscription;
    
    protected override async Task OnInitializedAsync()
    {
         _subscription = ApplicationState.RegisterOnPersisting(PersistForecasts);
    }
    

    OnPersisting will fire once your component has been loaded and rendered (which in a prerendering scenario will happen on the server as part of the initial prerender).

    private Task PersistForecasts()
    {
        ApplicationState.PersistAsJson("fetchData", forecasts);
        return Task.CompletedTask;
    }
    

    Here we persist the forecasts data, giving it a key of fetchData.

    The next step is to actually check for, and use, this data if it’s available. This is the step that will typically occur when the component is rendered for the second time in the browser.

    private PersistingComponentStateSubscription _subscription;
    
    protected override async Task OnInitializedAsync()
    {
        _subscription = ApplicationState.RegisterOnPersisting(PeristForecasts);
    
        if (ApplicationState.TryTakeFromJson<WeatherForecast[]>("fetchData", out var stored))
            forecasts = stored;
        else
            forecasts = await WeatherForecastService.GetForecastAsync();
    }
    

    We now look for that persisted fetchData data (from the first render on the server) and use it if it exists.

    Otherwise we can continue as normal and retrieve the data (in this case via the WeatherForecastService). In this scenario this would only happen once, as part of the server prerender.

    The result appears seamless; when you visit the page you’ll see it come back quickly (because of the prerendering) and likely won’t even notice the second render (in the browser) because nothing on screen will change.

    One last point, make sure to dispose of the subscription (to avoid any potential memory leaks). You can do this by making your component implement IDisposable, then calling _subscription.Dispose().

    Complete Example

    Here’s the final code (omitting the HTML markup) with the key lines which run on the server (during prerendering) highlighted.

    @page "/fetchdata"
    @using UI.Shared
    @inject IWeatherForecastService WeatherForecastService
    @inject PersistentComponentState ApplicationState
    @implements IDisposable
    
    <!-- html here -->
    
    @code {
        private WeatherForecast[] forecasts;
        private PersistingComponentStateSubscription _subscription;
    
        protected override async Task OnInitializedAsync()
        {
            _subscription = ApplicationState.RegisterOnPersisting(PersistForecasts);
    
            if (ApplicationState.TryTakeFromJson<WeatherForecast[]>("fetchData", out var stored))
                forecasts = stored;
            else
                forecasts = await WeatherForecastService.GetForecastAsync();
        }
    
        private Task PersistForecasts()
        {
            ApplicationState.PersistAsJson("fetchData", forecasts);
            return Task.CompletedTask;
        }
    
        public void Dispose()
        {
            _subscription.Dispose();
        }
    
    }
    

    In Summary

    With .NET 6 you’re able to use PersistentComponentState to grab a snapshot of your component’s state during the initial prerender, then use it to hydrate the component when it’s rendered for the second time in the browser.

    This avoids retrieving the same data twice and avoids flashes of content during the second render, resulting in a more seamless experience for your users.

    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?