Best for
- Migrating a Blazor Server app from .NET 6 or .NET 7 to .NET 8+
- App currently uses AddServerSideBlazor() and MapBlazorHub() in Program.cs (or Startup.cs)
- App uses Pages/Host.cshtml (or Host.razor) as the host page with Component Tag Helpers
dotnet/skills/plugins/dotnet-aspnetcore/skills/convert-blazor-server-to-webapp/SKILL.md
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
Decision brief
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 w…
Compatibility matrix
| Platform | Status | Evidence | What to check |
|---|---|---|---|
| Codex | Not declared | No explicit evidence | Portability before use |
| Claude Code | Not declared | No explicit evidence | Portability before use |
| Cursor | Not declared | No explicit evidence | Portability before use |
| Gemini CLI | Not declared | No explicit evidence | Portability before use |
Installation
The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.
npx skills add https://github.com/dotnet/skills --skill "plugins/dotnet-aspnetcore/skills/convert-blazor-server-to-webapp"Inspect the Agent Skill "convert-blazor-server-to-webapp" from https://github.com/dotnet/skills/blob/1b896e91feb0f613cb54a914f1efd2897810ae02/plugins/dotnet-aspnetcore/skills/convert-blazor-server-to-webapp/SKILL.md at commit 1b896e91feb0f613cb54a914f1efd2897810ae02. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.
Workflow
Commit strategy: Commit after each logical step so the migration is reviewable and bisectable.
Update the .csproj file:
The old App.razor contains the component. This content moves to a new Routes.razor file so that App.razor can become the root HTML document component.
Move the HTML shell from Pages/Host.cshtml into the now-empty App.razor and transform it from a Razor Page into a Razor component:
Make the following changes to Program.cs (or Startup.cs if the app uses the older hosting pattern):
Permission review
The documentation asks the agent to create, modify, or delete local files.
### Step 1: Update the project fileThe documentation asks the agent to create, modify, or delete local files.
Update the `.csproj` file:Evidence record
| Signal | Value | Evidence type | Meaning |
|---|---|---|---|
| Quality score | 90/100 | Computed | Documentation, specificity, maintenance, and trust rules |
| Repository stars | 5,248 | Source | Repository attention, not individual Skill quality |
| Compatibility | 0 platforms | Source | Declared in the catalog source record |
| Usage guide | automated source guide | Editorial | Generated or reviewed according to the visible evidence level |
Pinned source
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:
<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:
<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:
Remove Razor Page directives — delete @page "/", @using Microsoft.AspNetCore.Components.Web, @namespace, and @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers.
Add component injection — if using environment-conditional error UI, add:
@inject IHostEnvironment Env
Fix the base tag — replace <base href="~/" /> with <base href="/" />.
Replace HeadOutlet Component Tag Helper — replace:
<component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
with:
<HeadOutlet @rendermode="InteractiveServer" />
Replace App Component Tag Helper with Routes — replace:
<component type="typeof(App)" render-mode="ServerPrerendered" />
with:
<Routes @rendermode="InteractiveServer" />
Replace Environment Tag Helpers — replace:
<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:
@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>
}
Update the Blazor script — replace:
<script src="_framework/blazor.server.js"></script>
with:
<script src="_framework/blazor.web.js"></script>
Add render mode import — add to _Imports.razor:
@using static Microsoft.AspNetCore.Components.Web.RenderMode
Delete 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):
Replace Blazor Server services — replace:
builder.Services.AddServerSideBlazor();
with:
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
If AddServerSideBlazor had options configured (e.g., circuit options, hub options, detailed errors), migrate them to AddInteractiveServerComponents:
// 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);
});
Replace Blazor endpoint mapping — replace:
app.MapBlazorHub();
with:
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.
Remove the fallback route — delete:
app.MapFallbackToPage("/_Host");
Remove explicit routing middleware — delete if present:
app.UseRouting();
Endpoint routing is the default and explicit UseRouting() is no longer needed.
Add antiforgery middleware — add after UseAuthentication/UseAuthorization if present:
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:
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 loadingFrequently asked questions
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 w…
The source record exposes this install command: npx skills add https://github.com/dotnet/skills --skill "plugins/dotnet-aspnetcore/skills/convert-blazor-server-to-webapp". Inspect the command and pinned source before running it.
Static rules flagged write-files in the source; the page lists the matching lines and excerpts.
Alternatives
Postpartum-genushyacinthus29/dotnet-skills
Build long-running .NET background services with `BackgroundService`, Generic Host, graceful shutdown, configuration, logging, and deployment patterns suited to workers and daemons.
vasilyu1983/AI-Agents-public
Guides iOS testing with XCTest, XCUITest, Swift Testing, simctl, and xcresult. Use when choosing destinations, controlling flakes, or parsing test artifacts for native apps.
garrytan/gbrain
Generate a publication-quality PDF from any brain page via the gstack make-pdf binary. Strips YAML frontmatter, sanitizes emoji, applies running headers and page numbers. Brain page is always the source of truth; PDF is a rendering.
NVIDIA/skills
How to swap the DeepStream CV detection model in the VSS Alerts Blueprint verification (2d_cv) mode - covers ONNX export, custom bbox parsers, compose mount gotchas, nvinfer config, runtime TRT engine build, deployment, and a segmentation-capable model addendum handoff.