AspNetCore.Docs/aspnetcore/blazor/webassembly-performance-bes...

9.8 KiB

title author description monikerRange ms.author ms.custom ms.date no-loc uid
ASP.NET Core Blazor WebAssembly performance best practices pranavkm Tips for increasing performance in ASP.NET Core Blazor WebAssembly apps and avoiding common performance problems. >= aspnetcore-2.1 riande mvc 09/09/2020
ASP.NET Core Identity
cookie
Cookie
Blazor
Blazor Server
Blazor WebAssembly
Identity
Let's Encrypt
Razor
SignalR
blazor/webassembly-performance-best-practices

ASP.NET Core Blazor WebAssembly performance best practices

By Pranav Krishnamoorthy

This article provides guidelines for ASP.NET Core Blazor WebAssembly performance best practices.

Avoid unnecessary component renders

Blazor's diffing algorithm avoids rerendering a component when the algorithm perceives that the component hasn't changed. Override xref:Microsoft.AspNetCore.Components.ComponentBase.ShouldRender%2A?displayProperty=nameWithType for fine-grained control over component rendering.

If authoring a UI-only component that never changes after the initial render, configure xref:Microsoft.AspNetCore.Components.ComponentBase.ShouldRender%2A to return false:

@code {
    protected override bool ShouldRender() => false;
}

Most apps don't require fine-grained control, but xref:Microsoft.AspNetCore.Components.ComponentBase.ShouldRender%2A can be used to selectively render a component responding to a UI event. Using xref:Microsoft.AspNetCore.Components.ComponentBase.ShouldRender%2A might also be important in scenarios where a large number of components are rendered. Consider a grid, where use of xref:Microsoft.AspNetCore.Components.EventCallback in one component in one cell of the grid calls xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A on the grid. Calling xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A causes a re-render of every child component. If only a small number of cells require rerendering, use xref:Microsoft.AspNetCore.Components.ComponentBase.ShouldRender%2A to avoid the performance penalty of unnecessary renders.

In the following example:

<p>Current count: @currentCount</p>

<button @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;
    private bool shouldRender;

    protected override bool ShouldRender() => shouldRender;

    protected override void OnAfterRender(bool first)
    {
        shouldRender = false;
    }

    private void IncrementCount()
    {
        currentCount++;
        shouldRender = true;
    }
}

For more information, see xref:blazor/components/lifecycle#after-component-render.

Virtualize re-usable fragments

Components offer a convenient approach to produce re-usable fragments of code and markup. In general, we recommend authoring individual components that best align with the app's requirements. One caveat is that each additional child component contributes to the total time it takes to render a parent component. For most apps, the additional overhead is negligible. Apps that produce a large number of components should consider using strategies to reduce processing overhead, such as limiting the number of rendered components.

For more information, see xref:blazor/components/virtualization.

Avoid JavaScript interop to marshal data

In Blazor WebAssembly, a JavaScript (JS) interop call must traverse the WebAssembly-JS boundary. Serializing and deserializing content across the two contexts creates processing overhead for the app. Frequent JS interop calls often adversely affects performance. To reduce the marshalling of data across the boundary, determine if the app can consolidate many small payloads into a single large payload to avoid the high volume of context switching between WebAssembly and JS.

Use System.Text.Json

Blazor's JS interop implementation relies on xref:System.Text.Json, which is a high-performance JSON serialization library with low memory allocation. Using xref:System.Text.Json doesn't result in additional app payload size over adding one or more alternate JSON libraries.

For migration guidance, see How to migrate from Newtonsoft.Json to System.Text.Json.

Use synchronous and unmarshalled JS interop APIs where appropriate

Blazor WebAssembly offers two additional versions of xref:Microsoft.JSInterop.IJSRuntime over the single version available to Blazor Server apps:

  • xref:Microsoft.JSInterop.IJSInProcessRuntime allows invoking JS interop calls synchronously, which has less overhead than the asynchronous versions:

    @inject IJSRuntime JS
    
    @code {
        protected override void OnInitialized()
        {
            var jsInProcess = (IJSInProcessRuntime)JS;
    
            var value = jsInProcess.Invoke<string>("jsInteropCall");
        }
    }
    
  • xref:Microsoft.JSInterop.WebAssembly.WebAssemblyJSRuntime permits unmarshalled JS interop calls:

    function jsInteropCall() {
      return BINDING.js_to_mono_obj("Hello world");
    }
    
    @inject IJSRuntime JS
    
    @code {
        protected override void OnInitialized()
        {
            var jsInProcess = (WebAssemblyJSRuntime)JS;
    
            var value = jsInProcess.InvokeUnmarshalled<string>("jsInteropCall");
        }
    }
    

    [!WARNING] While using xref:Microsoft.JSInterop.WebAssembly.WebAssemblyJSRuntime has the least overhead of the JS interop approaches, the JavaScript APIs required to interact with these APIs are currently undocumented and subject to breaking changes in future releases.

Reduce app size

::: moniker range=">= aspnetcore-5.0"

Intermediate Language (IL) trimming

Trimming unused assemblies from a Blazor WebAssembly app reduces the app's size by removing unused code in the app's binaries. By default, the Trimmer is executed when publishing an application. To benefit from trimming, publish the app for deployment using the dotnet publish command with the -c|--configuration option set to Release:

::: moniker-end

::: moniker range="< aspnetcore-5.0"

Intermediate Language (IL) linking

Linking a Blazor WebAssembly app reduces the app's size by trimming unused code in the app's binaries. By default, the Intermediate Language (IL) Linker is only enabled when building in Release configuration. To benefit from this, publish the app for deployment using the dotnet publish command with the -c|--configuration option set to Release:

::: moniker-end

dotnet publish -c Release

Lazy load assemblies

Load assemblies at runtime when the assemblies are required by a route. For more information, see xref:blazor/webassembly-lazy-load-assemblies.

Compression

When a Blazor WebAssembly app is published, the output is statically compressed during publish to reduce the app's size and remove the overhead for runtime compression. Blazor relies on the server to perform content negotation and serve statically-compressed files.

After an app is deployed, verify that the app serves compressed files. Inspect the Network tab in a browser's Developer Tools and verify that the files are served with Content-Encoding: br or Content-Encoding: gz. If the host isn't serving compressed files, follow the instructions in xref:blazor/host-and-deploy/webassembly#compression.

Disable unused features

Blazor WebAssembly's runtime includes the following .NET features that can be disabled if the app doesn't require them for a smaller payload size:

  • A data file is included to make timezone information correct. If the app doesn't require this feature, consider disabling it by setting the BlazorEnableTimeZoneSupport MSBuild property in the app's project file to false:

    <PropertyGroup>
      <BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport>
    </PropertyGroup>
    

::: moniker range=">= aspnetcore-5.0"

  • By default, Blazor WebAssembly carries globalization resources required to display values, such as dates and currency, in the user's culture. If the app doesn't require localization, you may configure the app to support the invariant culture, which is based on the en-US culture:

    <PropertyGroup>
      <InvariantGlobalization>true</InvariantGlobalization>
    </PropertyGroup>
    

::: moniker-end

::: moniker range="< aspnetcore-5.0"

  • Collation information is included to make APIs such as xref:System.StringComparison.InvariantCultureIgnoreCase?displayProperty=nameWithType work correctly. If you're certain that the app doesn't require the collation data, consider disabling it by setting the BlazorWebAssemblyPreserveCollationData MSBuild property in the app's project file to false:

    <PropertyGroup>
      <BlazorWebAssemblyPreserveCollationData>false</BlazorWebAssemblyPreserveCollationData>
    </PropertyGroup>
    

::: moniker-end