20 April 2026

Blazor Server Migration from .NET 9 to .NET 10: Lessons Learned

Why wait for .NET 10 to migrate?

I had been looking forward to this release, mainly because of a Blazor Server reconnection bug I reported myself : https://github.com/dotnet/aspnetcore/issues/64228 . This issue affected the reliability of automatic reconnection after a network drop, and it was a real problem for a B2B application used continuously throughout the day. The fix is finally included in .NET 10, which made the migration even more worthwhile.

1. Update the .csproj

First step, no surprise here: update the target framework in the project file.

<TargetFramework>net10.0</TargetFramework>
XML

This is the mandatory starting point before any other change.

2. Update NuGet packages

Once the framework is updated, all packages need to follow. I updated the Microsoft dependencies (Microsoft.AspNetCore.*, etc.) to their 10.x versions, along with any third-party packages compatible with .NET 10. It is also a good opportunity to check for incompatibilities and clean up obsolete dependencies.

3. New Blazor structure files

This is where .NET 10 brings the most structural changes. I created a blank Blazor Server template project to extract the updated files.

NotFound.razor

A dedicated 404 page is now part of the default template. It is registered directly in Routes.razor via the new NotFoundPage attribute:

<Router AppAssembly="typeof(App).Assembly"
        NotFoundPage="typeof(Pages.NotFound)" />
Razor

No more inline <NotFound> block inside the router, which makes things cleaner and better separated.

ReconnectModal: reconnection rethought

The reconnection handling has also changed: it is now encapsulated in three dedicated files:

  • ReconnectModal.razor for the UI component
  • ReconnectModal.razor.cs for the code-behind
  • ReconnectModal.razor.js for the client-side reconnection logic

This component goes into the <body> of App.razor:

<body>
    ...
    <ReconnectModal />
    <Routes />
</body>
Razor

i18n bonus: I took the opportunity to customize ReconnectModal.razor.js to handle automatic translation of the reconnection messages. The code is available on my GitHub: tossnet/Blazor-Reconnect-demo-i18n

4. The Blazor script becomes a static asset

A notable change in App.razor: the Blazor script now goes through the static asset system. You use @Assets[] instead:

<script src="@Assets["_framework/blazor.web.js"]"></script>
Razor

This lets the framework handle cache-busting automatically through a fingerprint on the file name.

5. ResourcePreloader in the <head>

One last thing not to forget: the <ResourcePreloader /> component to add in the <head>:

<head>
    ...
    <ResourcePreloader />
</head>
Razor

It injects the appropriate <link rel="preload"> tags for static assets, which improves performance on the initial load.

Summary of changes

  1. Update <TargetFramework>net10.0</TargetFramework> in the .csproj
  2. Update NuGet packages to .NET 10 compatible versions
  3. Grab NotFound.razor, ReconnectModal.razor, .razor.cs and .razor.js from a template project
  4. Add NotFoundPage="typeof(Pages.NotFound)" on <Router /> in Routes.razor
  5. Replace the Blazor script with <script src="@Assets["_framework/blazor.web.js"]"></script>
  6. Add <ReconnectModal /> in the <body> and <ResourcePreloader /> in the <head>

Going further

I recorded a DevApps episode (in French) covering all the Blazor .NET 10 new features: watch it on YouTube.

The migration is fairly smooth overall. The biggest change is the restructuring of the reconnection handling and static assets, which is also a good opportunity to tailor the behavior to your needs.

Leave a Reply