Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Guides conversion of a pre-.NET 8 Blazor Server app into a .NET 8+ Blazor Web App. USE FOR: migrating apps that use AddServerSideBlazor and MapBlazorHub to the AddRazorComponents/MapRazorComponents model, converting _Host.cshtml to an App.razor root component, replacing blazor.server.js with blazor.web.js, migrating CascadingAuthenticationState to a service, adopting new Blazor Web App features like enhanced navigation and streaming rendering. DO NOT USE FOR: apps that are already Blazor Web App
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 101% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 94% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 173% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 169% | 0% |
| case-06 | ✓→✓ | = Same ✓ | 131% | 0% |
This skill helps an agent convert a pre-.NET 8 Blazor Server app into a .NET 8+ Blazor Web App. The old hosting model uses AddServerSideBlazor/MapBlazorHub with a _Host.cshtml Razor Page as the entry point. The new Blazor Web App model uses AddRazorComponents/MapRazorComponents with an App.razor root component, enabling per-component render modes, enhanced navigation, streaming rendering, and other .NET 8+ features. The converted app uses InteractiveServer render mode to preserve existing interactive behavior.
AddServerSideBlazor() and MapBlazorHub() in Program.cs (or Startup.cs)Pages/_Host.cshtml (or _Host.razor) as the host page with Component Tag HelpersAddRazorComponents and MapRazorComponents. It is already a Blazor Web App — no conversion is needed. Stop here and tell the user the app is already using the Blazor Web App model.| Input | Required | Description | |-------|----------|-------------| | Blazor Server project | Yes | The .csproj and source files of the Blazor Server app | | Target framework | Yes | .NET 8 or later (e.g., net8.0, net9.0, net10.0) | | Program.cs or Startup.cs | Yes | The app's service and middleware configuration | | _Host.cshtml location | Recommended | Usually Pages/_Host.cshtml; may be _Host.razor in some projects |
> Commit strategy: Commit after each logical step so the migration is reviewable and bisectable.
Update the .csproj file:
xml <TargetFramework>net8.0</TargetFramework>
Microsoft.AspNetCore.*, Microsoft.EntityFrameworkCore.*, Microsoft.Extensions.*, and System.Net.Http.Json package references to the matching version.For non-Blazor project file changes (nullable reference types, implicit usings, HTTP/3 support, etc.), see the general ASP.NET Core migration guide.
Routes.razor from App.razorThe old App.razor contains the <Router> component. This content moves to a new Routes.razor file so that App.razor can become the root HTML document component.
Routes.razor in the project root.App.razor into Routes.razor.<CascadingAuthenticationState>, remove that wrapper (it will be replaced by a service in Step 5).App.razor empty for the next step.The resulting Routes.razor should look similar to:
razor<Router AppAssembly="@typeof(Program).Assembly"> <Found Context="routeData"> <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" /> <FocusOnNavigate RouteData="@routeData" Selector="h1" /> </Found> <NotFound> <LayoutView Layout="@typeof(MainLayout)"> <p>Sorry, there's nothing at this address.</p> </LayoutView> </NotFound> </Router>
If the app uses <AuthorizeRouteView> instead of <RouteView>, keep it — it works the same way in Blazor Web Apps.
_Host.cshtml to App.razorMove the HTML shell from Pages/_Host.cshtml into the now-empty App.razor and transform it from a Razor Page into a Razor component:
@page "/", @using Microsoft.AspNetCore.Components.Web, @namespace, and @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers.razor @inject IHostEnvironment Env
<base href="~/" /> with <base href="/" />.html <component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" /> with: razor <HeadOutlet @rendermode="InteractiveServer" />
html <component type="typeof(App)" render-mode="ServerPrerendered" /> with: razor <Routes @rendermode="InteractiveServer" />
html <environment include="Staging,Production"> An error has occurred. This application may no longer respond until reloaded. </environment> <environment include="Development"> An unhandled exception has occurred. See browser dev tools for details. </environment> with: razor @if (Env.IsDevelopment()) { <text> An unhandled exception has occurred. See browser dev tools for details. </text> } else { <text> An error has occurred. This app may no longer respond until reloaded. </text> }
html <script src="_framework/blazor.server.js"></script> with: html <script src="_framework/blazor.web.js"></script>
_Imports.razor:razor @using static Microsoft.AspNetCore.Components.Web.RenderMode
Pages/_Host.cshtml (and Pages/_Host.cshtml.cs if it exists).Prerendering note: If the original app used render-mode="Server" (not "ServerPrerendered"), prerendering was disabled. Preserve this by using new InteractiveServerRenderMode(prerender: false) instead of InteractiveServer for both HeadOutlet and Routes.
Program.csMake the following changes to Program.cs (or Startup.cs if the app uses the older hosting pattern):
csharp builder.Services.AddServerSideBlazor(); with: csharp builder.Services.AddRazorComponents() .AddInteractiveServerComponents();
If AddServerSideBlazor had options configured (e.g., circuit options, hub options, detailed errors), migrate them to AddInteractiveServerComponents: csharp // Old: builder.Services.AddServerSideBlazor(options => { options.DetailedErrors = true; options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10); });
// New: builder.Services.AddRazorComponents() .AddInteractiveServerComponents(options => { options.DetailedErrors = true; options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10); });
csharp app.MapBlazorHub(); with: csharp app.MapRazorComponents<App>() .AddInteractiveServerRenderMode();
Ensure there is a using statement for the project's root namespace so that App resolves to the App.razor component.
csharp app.MapFallbackToPage("/_Host");
csharp app.UseRouting(); Endpoint routing is the default and explicit UseRouting() is no longer needed.
UseAuthentication/UseAuthorization if present:csharp app.UseAntiforgery(); AddRazorComponents registers antiforgery services automatically, but the middleware must be explicitly added to the pipeline. Without it, form POST requests fail with 400 errors.
CascadingAuthenticationState (if present)If the app used <CascadingAuthenticationState> to wrap the router:
<CascadingAuthenticationState> component wrapper (already done in Step 2 if following this workflow).Program.cs:csharp builder.Services.AddCascadingAuthenticationState();
The component wrapper approach does not work across render mode boundaries in Blazor Web Apps. The service-based approach provides Task<AuthenticationState> as a cascading value to all components regardless of render mode.
These are optional modernization improvements — not required for the conversion to work. If you suggest any of these, state explicitly that they are optional.
UseStaticFiles with MapStaticAssets (.NET 9+): app.MapStaticAssets() provides optimized static file serving with fingerprinting, pre-compression, and content-based ETags. See MapStaticAssets documentation.@attribute [StreamRendering] to pages with async data loading (OnInitializedAsync) for improved perceived performance. The page renders its initial synchronous content immediately and re-renders when async data arrives.<link> tag referenced a _Host assembly name; ensure it matches the project's actual assembly name: <link href="{AssemblyName}.styles.css" rel="stylesheet" />.AddServerSideBlazorMapBlazorHubMapFallbackToPageblazor.server.js_Host.cshtmlAddServerSideBlazor remainMapBlazorHub remainMapFallbackToPage("/_Host") remainblazor.server.js remainPages/_Host.cshtml has been deletedApp.razor serves as the root component with a full HTML document structureRoutes.razor contains the <Router> configurationProgram.cs uses AddRazorComponents().AddInteractiveServerComponents()Program.cs uses MapRazorComponents<App>().AddInteractiveServerRenderMode()app.UseAntiforgery() is present in the middleware pipeline<CascadingAuthenticationState>, it has been replaced with AddCascadingAuthenticationState() service registration| Pitfall | Solution | |---------|----------| | Missing UseAntiforgery() middleware | AddRazorComponents registers antiforgery services, but the middleware must be explicitly added. Place app.UseAntiforgery() after UseAuthentication/UseAuthorization. Without it, form POST requests fail with 400 errors. | | Forgetting to replace blazor.server.js with blazor.web.js | The old script does not work with the Blazor Web App model. Replace all references to _framework/blazor.server.js with _framework/blazor.web.js. | | Not removing <CascadingAuthenticationState> wrapper | The component wrapper does not work across render mode boundaries in Blazor Web Apps. Use builder.Services.AddCascadingAuthenticationState() instead. | | Leaving app.UseRouting() in the pipeline | Explicit UseRouting() is no longer needed and can interfere with endpoint routing. Remove it unless other middleware specifically requires it. | | Using InteractiveServer when prerendering was disabled | If the original app used render-mode="Server" (not "ServerPrerendered"), use new InteractiveServerRenderMode(prerender: false) to preserve the same behavior. Using InteractiveServer enables prerendering which can cause unexpected issues with components that depend on JS interop during initialization. | | Not migrating AddServerSideBlazor circuit options | If circuit options, hub options, or detailed error settings were configured, migrate them to AddInteractiveServerComponents(options => { ... }). Otherwise those settings are silently lost. | | UseAntiforgery() placed before authentication middleware | The antiforgery middleware must be placed after UseAuthentication and UseAuthorization. Placing it before causes antiforgery validation to run before the user identity is established. | | CSS isolation bundle link has wrong assembly name | If the <link href="{Name}.styles.css"> tag referenced the old project name, update it to match the current assembly name. |
@attribute [StreamRendering] for async data loadingOther measured skills in the registry, with their headline benchmark lift.