Not only is .NET 10 the next long-term support version, it also delivers improved startup time, state management, JavaScript interop and NotFound handling. In short, YES! It’s worth updating your Blazor app to .NET 10!
In this article, I want to share what’s new in .NET 10 for Blazor development. You will learn about new features and their impact on everyday web development with Blazor.
Besides writing articles for the Blazor Basics series on the Progress Telerik blog, I have two Blazor web applications of different sizes in production for my customers.
If you are still using .NET 8 because of its long-term support (LTS) policy, let me convince you to upgrade your apps to .NET 10. It is, after all, the next LTS version that will be supported by Microsoft for three years.
One of the less developer-focused, but no less critical, updates is that the blazor.web.js file is now pre-compressed using the Brotli/gzip algorithm and fingerprinting.
The pre-compression reduces the download size for the whole Blazor engine from around 200 KB to less than 50 KB per application.
Note: Those numbers are not from artificial testing or benchmarking, but from observing real-world applications.
This reduction in payload size primarily yields faster startup performance and, therefore, a better user experience. All you have to do to take advantage of it is upgrade your application to use .NET 10.
Who doesn’t like free performance gains for their apps?
The PersistentComponentState service allows us to persist state during prerendering. Prerendering is a complex topic explained in the Blazor Basics: Prerendering Server Components in Blazor article.
Before .NET 10, we had to write a lot of code to handle state persistence during prerendering properly. It not only includes many lines of code that bloat the application and become hard to maintain, but it is also an error-prone area.
I have seen developers implementing slightly wrong or completely wrong code when using the PersistentComponentState service.
Starting with .NET 10, we can use the PersistentState attribute. It allows for a declarative (compared to an imperative) way to declare what data should be persisted during prerendering.
The following example demonstrates that we can now persist the state of the CustomerList property during prerendering by applying the PersistentState attribute.
@code {
[PersistentState]
public List<Customers>? CustomerList { get; set; }
protected override async Task OnInitializedAsync()
{
CustomerList ??= await CustomerService.GetCustomers();
}
}
You can explore more information, including detailed example code, in the official Microsoft documentation on persisting state.
Starting with .NET 10, we can now create an instance of a JavaScript object and provide arguments to the constructor parameters.
var personRef = await JSRuntime.InvokeConstructorAsync("jsInterop.PersonClass", "Claudio");
var name = await personRef .GetValueAsync<string>("name");
var nameLength = await personRef .InvokeAsync<int>("getNameLength");
This example code assumes a JavaScript PersonClass class with a name field and a getNameLength function exists.
Setting values to a property is also possible using the SetValueAsync method.
If you don’t have to use JavaScript interop, you might not be able to tell how much these new APIs will improve the developer experience.
However, I previously had to make heavy use of JavaScript interop, and I had to work my way around the limitations. The code will definitely become simpler and shorter using these new APIs.
The NotFound property of the Router (found in Routes.razor) component allows us to define what component to render when a route doesn’t match any registered page components.
However, until .NET 10, there was no simple way to handle the case where a route matches a page, but the application logic determines that no resource exists at that route.
Consider the following example:
Let’s say we have a customers page that lists all our customers. The user can click on the name of the customer to load a detail page about the customer containing information such as open invoices and their purchase history.
Let’s say there is a broken link in the application, or the user puts a random id into the browser’s address. Blazor will find and render the correct customer details page. However, we will not be able to find the customer in the database.
Up to .NET 9, we may have handled that case using the following code:
protected override void OnInitialized()
{
CustomerItem = CustomerService.GetCustomer(CustomerId);
if (CustomerItem == null)
{
NavigationManager.NavigateTo("/not-found");
}
}
We have to manually redirect to the error page. There are two main flaws with this approach:
Starting with .NET 10, there is a new NotFound method on the NavigationManager class.
We can use the following code:
protected override void OnInitialized()
{
CustomerItem = CustomerService.GetCustomer(CustomerId);
if (CustomerItem == null)
{
NavigationManager.NotFound();
}
}
Calling the NotFound method on the NavigationManager fixed both problems described above.
The user will be redirected to the general NotFound error page (see NotFound.razor in projects generated using the .NET 10 project templates). And it will correctly set 404 as the HTTP Status code for the request.
The best part is that we can now use this identical method for Static Server-Rendered Pages, Streaming SSR, Blazor Server and Blazor WebAssembly interactivity.
You can access the code of the working example on GitHub.
The features and improvements mentioned in this article have the most significant impact on everyday Blazor web development. At least, they have a positive impact on my real-world applications running in my clients’ businesses.
With the new JavaScript interop methods, I write less and more efficient interop code.
With the PersistentState attribute, handling state persistence using component prerendering in Blazor Server is finally understandable without being an expert Blazor developer.
And the NotFound method on the NavigationManager provides a consistent, correct experience across all Blazor rendering modes by implementing proper behavior for resources that do not exist.
There are obviously many more improvements in .NET 10, such as built-in Blazor metrics for ASP.NET Core, tracing of Blazor Server circuits, Blazor WebAssembly memory profiling support and improved Blazor web application project templates.
Some of them are not limited to Blazor, but improve the developer and user experience across other parts of the ASP.NET Core web framework or even the whole .NET platform. For example, the general performance improvements from .NET 9 to .NET 10.
If you made it this far, I hope I have convinced you to start upgrading your Blazor web application from .NET 9 or even .NET 8 to .NET 10. It’s worth it.
If you want to learn more about Blazor development, watch my free Blazor Crash Course on YouTube. And stay tuned to the Telerik blog for more Blazor Basics.
Claudio Bernasconi is a passionate software engineer and content creator writing articles and running a .NET developer YouTube channel. He has more than 10 years of experience as a .NET developer and loves sharing his knowledge about Blazor and other .NET topics with the community.