Blazor in .NET 11: What Changed and What Breaks
Last updated: Nov 12, 2026 · Tested with ASP.NET Core 11.0.0 (SDK 11.0.100) and 10.0.12 (SDK 10.0.401), headless Edge 157, Windows 11
Short answer: Blazor in .NET 11 is mostly an additive release. Static SSR gets the pieces it was missing: TempData, session-backed parameters, a CacheView output cache and client-side form validation. Virtualize keeps 15 items above and below the viewport instead of 3, works under a strict CSP and can open at any item. WebAssembly apps can be configured from C# on the server. Four things can break an upgraded app. WebAssembly now reads environment variables into IConfiguration, so they can override appsettings.json. registerCustomEventType throws on a name clash. The removed obsolete APIs stop the build. And one TempData pattern from the docs doesn’t behave as documented, even in the GA release.
I built the same small Blazor Web App for net10.0 and net11.0 and ran both side by side, first on a release candidate and again on the 11.0.0 GA release. Everything below is copied from the GA runs. Apart from timestamps and generated IDs, GA gave the same results as the release candidate.
| Change | .NET 10.0.12 | .NET 11.0.0 |
|---|---|---|
| Flash messages in static SSR | Not built in | ITempData, [SupplyParameterFromTempData] |
| Session values in static SSR | Not built in | [SupplyParameterFromSession] |
| Cache part of a page | Whole-response output caching only | <CacheView VaryByQuery="..."> |
| SSR form validation | Server round trip | In the browser, no POST until valid |
Virtualize items in the DOM (300px list, 30px rows) | 20 | 44 |
Virtualize under style-src 'self' | Broken: 5 CSP violations, list can’t scroll | Works, 0 violations |
WASM env var in IConfiguration | (null) | Visible, and it overrides appsettings.json |
registerCustomEventType('x', { browserEventName: 'x' }) | Handler fires twice per event | Throws Error |
InvokeAsync<object> | No warning | BL0010 and BL0016 warnings |
Two Blazor changes are already covered in the ASP.NET Core 11 breaking changes post: SSR form posts without UseAntiforgery(), and QuickGrid rendering links instead of buttons. I don’t repeat them here.
How I Tested It
One Blazor Web App (Lab) with a WebAssembly client project (Lab.Client), Interactive Server and Interactive WebAssembly render modes. A shared Directory.Build.props picks the target and the matching package version, so the same source builds twice:
<AspVersion Condition="'$(TF)' == 'net10.0'">10.0.12</AspVersion>
<AspVersion Condition="'$(TF)' == 'net11.0'">11.0.0</AspVersion>
net10.0 builds with the machine’s SDK 10.0.401. net11.0 builds with a portable copy of SDK 11.0.100. Pages that use .NET 11-only APIs sit in a Components/Net11 folder that the net10.0 build leaves out. Both apps ran on Kestrel on localhost in the Development environment, on ports 5481 and 5482. I checked server-rendered HTML with curl. For anything that needs a running circuit or the WebAssembly runtime, a Node script drove headless Edge 157 over the DevTools protocol: it clicked, scrolled, dispatched DOM events and read the DOM and network traffic back. I also generated dotnet new blazor and dotnet new blazorwasm projects with both SDKs and diffed them.
1. What the .NET 11 Blazor Template Changes
If you compare a fresh template with your own project, these are the differences that matter. In App.razor, the hard-coded base tag is now a component:
.NET 10: <base href="/" />
.NET 11: <BasePath />
At the root of the app <BasePath /> renders exactly what it replaced, <base href="/" />. The NavMenu no longer closes the mobile menu with an inline handler:
.NET 10: <div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()">
In .NET 11 the markup is wrapped in a <nav-menu> custom element. A collocated NavMenu.razor.js module defines that element and handles the click. An inline onclick needs 'unsafe-inline' or a hash in a Content Security Policy, and the module needs neither. If you’re adding a CSP to an existing app, copy this change.
In Program.cs, app.UseAntiforgery() is gone (Learn: CSRF protection is now on by default through an auto-injected middleware, so the explicit call is usually unnecessary). The Auto/WebAssembly template also drops app.UseWebAssemblyDebugging(), and the inspectUri lines are gone from launchSettings.json. The standalone WebAssembly template swaps its dev server package:
.NET 10: <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.12" PrivateAssets="all" />
.NET 11: <PackageReference Include="Microsoft.AspNetCore.Components.Gateway" Version="11.0.0" PrivateAssets="all" />
.NET 11: <StaticWebAssetSpaFallbackEnabled>true</StaticWebAssetSpaFallbackEnabled>
You don’t have to switch right away. I took the .NET 10 standalone template, changed only the target framework and package versions, and it ran on 11.0.0. It served /, the /counter deep link and blazor.webassembly.js with 200, but it logged this on startup:
Microsoft.AspNetCore.Components.WebAssembly.DevServer is deprecated and will be removed in a future release. Use Microsoft.AspNetCore.Components.Gateway instead.
The new template with Gateway served the same three URLs with 200. Two new templates also appeared in dotnet new list blazor:
Blazor Web Worker blazorwebworker [C#] Web/Blazor/WebAssembly/Worker/Library
Blazor WebAssembly Service Defaults blazorwasm-servicedefaults [C#] Blazor/WebAssembly/Library/OpenTelemetry
The release notes call the second one blazor-wasm-servicedefaults. In SDK 11.0.100 the short name has no hyphen, so dotnet new blazorwasm-servicedefaults is the one that works.
2. TempData and Session in Static SSR
Before .NET 11, a static SSR form that wanted to show “Saved” after a redirect had to put the message in the query string or write its own cookie. Now there’s a cookie-based TempData and an attribute that reads session values. Here’s the documented attribute version of the POST-redirect-GET pattern:
<form method="post" @formname="flash" @onsubmit="Save">
<AntiforgeryToken />
<button type="submit">Save</button>
</form>
<p id="message">Message: @(Message ?? "(none)")</p>
@code {
[SupplyParameterFromTempData]
public string? Message { get; set; }
private void Save()
{
Message = $"Saved at {DateTime.Now:HH:mm:ss}";
Navigation.NavigateTo("/tempdata");
}
}
Nothing to register: AddRazorComponents() is enough. curl with a cookie jar, posting the form and then loading the page three times:
POST /tempdata -> HTTP/1.1 302 Found, Location: http://localhost:5482/tempdata, cookies set: .AspNetCore.Components.TempData
GET /tempdata (1): <p id="message">Message: Saved at 08:35:48, Set-Cookie: .AspNetCore.Components.TempData
GET /tempdata (2): <p id="message">Message: Saved at 08:35:48, Set-Cookie: .AspNetCore.Components.TempData
GET /tempdata (3): <p id="message">Message: Saved at 08:35:48, Set-Cookie: .AspNetCore.Components.TempData
This is the gotcha. The Learn page says TempData “is automatically removed after the data is read”. Through [SupplyParameterFromTempData] it wasn’t. Every response wrote the cookie again with the same value, so the “one-time” message showed on every reload. My guess is that the property still holds the value when the request ends and gets saved again, but I haven’t confirmed that in the source. The ITempData cascading parameter with Get behaved as documented:
[CascadingParameter]
public ITempData? TempData { get; set; }
private string? message;
protected override void OnInitialized() => message = TempData?.Get("Note") as string;
private void Save()
{
TempData!["Note"] = $"Saved at {DateTime.Now:HH:mm:ss}";
Navigation.NavigateTo("/tempdata-get");
}
POST /tempdata-get -> HTTP/1.1 302 Found
GET /tempdata-get (1): <p id="message">Message: Saved at 08:34:47
GET /tempdata-get (2): <p id="message">Message: (none)
GA didn’t change this. The release candidate wrote the same cookie on every GET, and so did 11.0.0. For flash messages, use ITempData.Get.
[SupplyParameterFromSession] is the persistent version. It needs AddDistributedMemoryCache(), AddSession() and app.UseSession(), and a value you assign in a handler is saved for you:
[SupplyParameterFromSession]
public int? CartCount { get; set; }
private void Add() => CartCount = (CartCount ?? 0) + 1;
POST /session (1): <p id="cart">Cart items: 1
POST /session (2): <p id="cart">Cart items: 2
GET /session: <p id="cart">Cart items: 2
GET /session, no cookies: <p id="cart">Cart items: 0
Both only work in static SSR. Per the docs, under an interactive render mode the property keeps its default value. Both also take only simple types: strings, numbers, Guid, DateTime, int-backed enums and collections of those, not your own classes. Session state lives on one server unless you configure a distributed cache, so plan for sticky sessions or Redis behind a load balancer.
On .NET 10 these pages don’t compile:
TempDataPage.razor(11,6): error CS0246: The type or namespace name 'SupplyParameterFromTempData' could not be found (are you missing a using directive or an assembly reference?)
SessionPage.razor(10,6): error CS0246: The type or namespace name 'SupplyParameterFromSession' could not be found (are you missing a using directive or an assembly reference?)
TempDataGetPage.razor(12,12): error CS0246: The type or namespace name 'ITempData' could not be found (are you missing a using directive or an assembly reference?)
3. CacheView: Output Caching for Part of a Page
CacheView caches the rendered HTML of its child content in static SSR. On a hit, the children aren’t created at all, so their OnInitialized and database calls don’t run. I gave a child component a static instance counter:
<p id="page">Page rendered at @DateTime.Now.ToString("HH:mm:ss.fff")</p>
<CacheView VaryByQuery="category" ExpiresAfter="TimeSpan.FromSeconds(30)">
<ExpensiveList Category="@Category" />
</CacheView>
GET /cache?category=books: Page rendered at 08:34:49.034|ExpensiveList for 'books': instance #1, rendered at 08:34:49.042
GET /cache?category=books: Page rendered at 08:34:49.376|ExpensiveList for 'books': instance #1, rendered at 08:34:49.042
GET /cache?category=games: Page rendered at 08:34:49.717|ExpensiveList for 'games': instance #2, rendered at 08:34:49.717
GET /cache?category=books: Page rendered at 08:34:50.088|ExpensiveList for 'books': instance #1, rendered at 08:34:49.042
(' is how Blazor encodes the apostrophe in the HTML.) The page itself rendered fresh every time, but ExpensiveList was created once per category. The second books request replayed instance #1’s markup, and so did the fourth. That’s the difference from the output caching middleware: the middleware caches the whole response, while CacheView leaves the rest of the page dynamic.
Besides VaryByQuery, the 11.0.0 component has VaryByRoute, VaryByHeader, VaryByCookie, VaryByUser, VaryByCulture, ExpiresOn and ExpiresSliding, and RazorComponentsServiceOptions has CacheViewSizeLimit and CacheViewHybridCache. If the fragment depends on the signed-in user, set VaryByUser, or one user’s markup will be served to another.
4. Client-Side Validation for Static SSR Forms
A static SSR EditForm with <DataAnnotationsValidator /> now validates in the browser before it posts. There’s no opt-in, and I didn’t change any markup. The model and the two new label components:
<p>
<Label For="() => Model!.Email" />
<InputText @bind-Value="Model!.Email" />
<ValidationMessage For="() => Model!.Email" />
</p>
<p>
<label><DisplayName For="() => Model!.Age" />: <InputNumber @bind-Value="Model!.Age" /></label>
<ValidationMessage For="() => Model!.Age" />
</p>
[Required, EmailAddress, Display(Name = "E-mail address")]
public string? Email { get; set; }
[Range(18, 120), DisplayName("Age in years")]
public int Age { get; set; }
The server renders an empty, hidden message element per field, plus the rules as JSON in a <blazor-client-validation-data> element at the end of the form. <Label> became a real <label for> with the [Display] name:
<p><label for="Model_Email">E-mail address</label>
<div class="validation-message" data-valmsg-for="Model.Email" data-valmsg-replace="true" hidden></div></p>
<button type="submit">Sign up</button><blazor-client-validation-data data-rules="{"fields":[{"name":"Model.Email","rules":[{"name":"required","message":"The E-mail address field is required."},{"name":"email","message":"The E-mail address field is not a valid e-mail address."}]},{"name":"Model.Age","rules":[{"name":"range","message":"The field Age in years must be between 18 and 120.","params":{"min":"18","max":"120"}}]}]}"></blazor-client-validation-data></form>
In Edge I clicked Submit three times while counting POST requests on the network:
/form empty form: POST requests sent: 0, validation messages: The E-mail address field is required. | The field Age in years must be between 18 and 120., result: ''
/form Email "not-an-email", Age 5: POST requests sent: 0, validation messages: The E-mail address field is not a valid e-mail address. | The field Age in years must be between 18 and 120., result: ''
/form Email "ann@example.com", Age 30: POST requests sent: 1, validation messages: | , result: 'server accepted ann@example.com, 30'
Invalid input never left the browser, and the messages are the same strings the server would produce. The server still validates the POST, so this saves a round trip but it isn’t a security check. If you have your own JavaScript validation on SSR forms, test for double messages after the upgrade. The switches are DisableClientValidation on DataAnnotationsValidator and on RazorComponentsServiceOptions. Async validators (EditContext.RegisterAsyncFieldValidator) are new in .NET 11 too, but I didn’t test them.
5. Virtualize: Bigger Overscan, CSP-Safe Spacers, Jump to an Item
The same component on both versions: 10,000 items, ItemSize="30", in a 300px scrolling div, Interactive Server:
<div id="list">
<Virtualize Items="items" ItemSize="30" Context="item">
<div class="row-item">Item @item</div>
</Virtualize>
</div>
.NET 10: /virt scrollTop 150000: 20 items in the DOM, Item 4995 .. Item 5014
.NET 11: /virt scrollTop 150000: 44 items in the DOM, Item 4983 .. Item 5026
That’s the new OverscanCount default, 15 instead of 3. Microsoft made the change so Virtualize can measure variable-height rows more accurately. Two things can show up after an upgrade. Each render now builds more than twice as many rows, which you’ll notice when the row template is heavy. And tests that count rendered rows will fail. Set OverscanCount="3" to get the old number back.
The spacers changed too. Here’s the server-rendered (prerendered) HTML:
.NET 10: <div aria-hidden="true" style="height: 300000px; flex-shrink: 0;">
.NET 11: <div aria-hidden="true" data-blazor-virtualize-reserved-height="300000">
On .NET 11 the height is a data attribute, and the script applies it through the CSSOM. A CSP of style-src 'self' blocks inline style attributes but not CSSOM writes. I sent that header on the same page:
.NET 10: /virt?csp=1 list scrollHeight 600, CSP violations: 5 (first: style-src-attr )
.NET 10: /virt?csp=1 after scrollTop = 150000: scrollTop 300: 20 items in the DOM, Item 0 .. Item 19
.NET 11: /virt?csp=1 list scrollHeight 300000, CSP violations: 0 (first: none)
.NET 11: /virt?csp=1 after scrollTop = 150000: scrollTop 150000: 44 items in the DOM, Item 4983 .. Item 5026
On .NET 10 the spacers got no height, the 10,000-row list was 600px tall, and you couldn’t scroll past item 19. On .NET 11 it works. If you dropped Virtualize or kept 'unsafe-inline' because of your CSP, this fixes it.
There are also new InitialItemIndex and ScrollToItemAsync members:
<Virtualize @ref="list" Items="items" ItemSize="30" InitialItemIndex="500" Context="item">
private async Task Jump()
{
await list.ScrollToItemAsync(9000);
status = "ScrollToItemAsync(9000) completed";
}
/virt-jump on load scrollTop 15000: 31 items in the DOM, Item 485 .. Item 515
/virt-jump after click scrollTop 270000: 31 items in the DOM, Item 8985 .. Item 9015, status 'ScrollToItemAsync(9000) completed'
15,000px is exactly 500 × 30, so the list opened at item 500 with no jump from the top. After the click the list was at 270,000px (item 9000), and the status line showed the awaited task had completed. There’s also an AnchorMode parameter (Start, End, None) for chat- and log-style lists. I didn’t test it.
6. WebAssembly Reads Environment Variables into IConfiguration
This is a listed breaking change. WebAssemblyHostBuilder.CreateDefault now adds environment variables to configuration, the same way WebApplication.CreateBuilder always has. I set two variables in the browser with the startup options that already existed in .NET 10:
Blazor.start({
webAssembly: {
configureRuntime: runtime => runtime
.withEnvironmentVariable('LAB_FROM_JS', 'set by configureRuntime')
.withEnvironmentVariable('Lab__Greeting', 'from environment variable')
}
}).then(() => labWrite('Blazor started'));
The client’s wwwroot/appsettings.json sets Lab:Greeting to from appsettings.json. A WebAssembly page (no prerendering) prints what it sees:
.NET 10: /wasm Environment.GetEnvironmentVariable("LAB_FROM_JS"): 'set by configureRuntime'
.NET 10: /wasm Configuration["LAB_FROM_JS"]: (null)
.NET 10: /wasm Configuration["Lab:Greeting"]: 'from appsettings.json'
.NET 11: /wasm Environment.GetEnvironmentVariable("LAB_FROM_JS"): 'set by configureRuntime'
.NET 11: /wasm Configuration["LAB_FROM_JS"]: 'set by configureRuntime'
.NET 11: /wasm Configuration["Lab:Greeting"]: 'from environment variable'
The last line is the one that can bite. An environment variable with a __ name now wins over appsettings.json in the browser. Check whether your hosting or Aspire setup passes variables into the WASM runtime that happen to match configuration keys. If you added builder.Configuration.AddEnvironmentVariables() yourself, you can remove it.
There’s also a new, supported way to set these values from the server, which replaces the JavaScript above:
components.WithBrowserOptions(options =>
options.InteractiveWebAssembly.EnvironmentVariables["LAB_FROM_SERVER"] = "set by WithBrowserOptions");
.NET 11: /wasm Configuration["LAB_FROM_SERVER"]: 'set by WithBrowserOptions'
The server writes the values into the page as a base64 JSON comment, which decodes to this:
{"webAssembly":{"environmentName":"Development","environmentVariables":{"LAB_FROM_SERVER":"set by WithBrowserOptions"}},"server":{},"ssr":{}}
Anyone can read that in View Source, so it’s not a place for secrets, just like appsettings.json in wwwroot. BrowserOptions also covers the reconnection dialog and retries, the log level, PreserveDom for enhanced navigation, and the WASM culture and environment name. That’s most of what people used to write Blazor.start scripts for. If you’re still choosing a hosting model, Blazor Server vs WebAssembly vs Auto compares them.
7. registerCustomEventType Throws on a Name Clash
This one shows up in the browser console, not in the build. My startup script registers two custom events wrapping the same DOM event, one with the same name and one with a different name. The page has handlers for both:
tryRegister('scrolltop', 'scrolltop');
tryRegister('customscroll', 'scrolltop');
<div id="target" @onscrolltop="OnScrollTop" @oncustomscroll="OnCustomScroll">target</div>
.NET 10: lab-start.js: registerCustomEventType('scrolltop', { browserEventName: 'scrolltop' }): ok
.NET 10: after one 'scrolltop' DOM event: onscrolltop: 2, oncustomscroll: 1
.NET 11: lab-start.js: registerCustomEventType('scrolltop', { browserEventName: 'scrolltop' }) threw Error: The custom event 'scrolltop' cannot have the same name as its browserEventName 'scrolltop'. Choose a different name for the custom event.
.NET 11: after one 'scrolltop' DOM event: onscrolltop: 1, oncustomscroll: 1
On .NET 10 the same-name registration “worked” and ran the C# handler twice for every event, which is a bug you might never have noticed. On .NET 11 it throws synchronously. My script had a try/catch. Without one, an exception at that point stops the rest of your startup script, and if Blazor.start() comes after it, Blazor never starts. You’d get a page that renders but doesn’t respond. If a component library registers custom events, search its JS for registerCustomEventType. The fix is to rename the custom event (for example customscroll) and update the matching [EventHandler] attribute.
8. New Analyzer Warnings: BL0010 and BL0016
The same Js.razor built clean on .NET 10 and with two warnings on 11.0.0:
await JS.InvokeAsync<object>("console.log", "hello from Blazor");
.NET 10: (no errors or warnings)
.NET 11: Js.razor(11,19): warning BL0010: Use 'InvokeVoidAsync' instead of 'InvokeAsync<object>'. Return values of type 'object' cannot be deserialized and may cause serialization errors if the JavaScript function returns a non-serializable value.
.NET 11: Js.razor(11,19): warning BL0016: JS interop call 'InvokeAsync' is not guarded with a try/catch block.
BL0010 is in the release notes. BL0016 isn’t, and it fires on every unguarded interop call, so a real app will get a lot of them. With TreatWarningsAsErrors on, both break the build. Fix BL0010 by switching to InvokeVoidAsync. For BL0016, decide whether you want try/catch around interop or a <NoWarn> entry.
9. Smaller Additions, and a Multi-Targeting Trap
On a page at /docs/misc:
<BasePath />
<EnvironmentView Include="Development"><p id="env-dev">only in Development</p></EnvironmentView>
<EnvironmentView Exclude="Development"><p id="env-not-dev">not Development</p></EnvironmentView>
<a id="fragment" href="@Navigation.GetUriWithFragment("install")">jump</a>
<NavLink id="relative" href="setup" RelativeToCurrentUri="true">setup (relative)</NavLink>
<NavLink id="absolute" href="setup">setup (default)</NavLink>
<base href="/" />
<p id="env-dev">only in Development
<a id="fragment" href="http://localhost:5482/docs/misc#install">
<a id="relative" href="http://localhost:5482/docs/setup">setup (relative)
<a id="absolute" href="setup">setup (default)
EnvironmentView is the Razor-component version of MVC’s <environment> tag helper. GetUriWithFragment builds a correct #anchor link even when there’s a <base href>. And RelativeToCurrentUri resolves setup against /docs/ instead of the app root. NavigationOptions has the same property for NavigateTo.
The trap: I compiled the .NET 11 pages against net10.0 to see the errors. Code with the new types fails. Markup with the new components doesn’t:
CachePage.razor(4,1): warning RZ10012: Found markup element with unexpected name 'CacheView'. If this is intended to be a component, add a @using directive for its namespace.
FormPage.razor(6,9): warning RZ10012: Found markup element with unexpected name 'Label'. If this is intended to be a component, add a @using directive for its namespace.
MiscPage.razor(5,1): warning RZ10012: Found markup element with unexpected name 'EnvironmentView'. If this is intended to be a component, add a @using directive for its namespace.
Those are warnings. Razor treats an unknown component as a plain HTML element, so a Razor class library that targets both net10.0 and net11.0 could use <EnvironmentView> and build fine. I didn’t run that build, but Razor writes an unknown element out with its children. So on .NET 10 the “Development only” content would show in every environment. Unknown parameters such as RelativeToCurrentUri on NavLink don’t even warn. They become HTML attributes. If you multi-target, promote RZ10012 to an error.
Enhanced Navigation Preloading: No Difference in My Test
The breaking changes list says Blazor no longer emits <link rel="preload"> hints for pages reached by enhanced navigation (dotnet/aspnetcore#63544). I clicked from an SSR page to a WebAssembly page in Edge and looked at the response the browser got:
.NET 10: enhanced nav GET /wasm, Accept: text/html; blazor-enhanced-nav=on
.NET 10: preload links in that response: 0
.NET 11: enhanced nav GET /wasm, Accept: text/html; blazor-enhanced-nav=on
.NET 11: preload links in that response: 0
A normal load of /wasm gets one dotnet.js preload link on both versions. With this app, 10.0.12 already behaves the way the docs describe for 11, so I saw nothing to fix.
What I Didn’t Test
- Variable-height rows in
Virtualize, andAnchorMode. My rows were a fixed 30px. - Async form validation,
IComponentPropertyActivator,ConfigureConnectionfor SignalR, and circuits picking up a refreshed sign-in. - Circuit pause:
Circuit.RequestCircuitPauseAsync, and theAutoPausepackage (separate download). - The experimental Blazor AI components (
Microsoft.AspNetCore.Components.AI). - The removed obsolete Blazor APIs. The ASP.NET Core 11 breaking changes post shows the compiler errors for
AddDataAnnotationsValidation,EnableDataAnnotationsValidation(EditContext)andRouter.PreferExactMatches. The WebAssembly authentication removals (SignOutSessionStateManager, theAccessTokenResultconstructor) are in a package I didn’t reference. - HTTPS, publish output, AOT and trimming. Everything ran from build output over HTTP in Development.
dotnet watchhot reload and WASM debugging through the new Gateway weren’t tested either. - Browsers other than Edge 157, and Linux or macOS servers.
Upgrade Checklist for a .NET 10 Blazor App
- Bump the
TargetFrameworkand everyMicrosoft.AspNetCore.Components.*package together, in the server and the client project. - Standalone WASM: swap
WebAssembly.DevServerforComponents.Gateway, addStaticWebAssetSpaFallbackEnabled, and removeinspectUrifromlaunchSettings.json. - Search your JS and libraries for
registerCustomEventTypecalls where the event name equalsbrowserEventName. - List the environment variables your WASM client gets and check none of them shadow
appsettings.jsonkeys. - Expect
BL0010andBL0016warnings. Decide on fixes orNoWarnbefore warnings-as-errors fails CI. - Re-check
Virtualizepages with heavy row templates (overscan 3 to 15), and UI tests that count rows. - If your SSR forms have custom JS validation, look for duplicate messages.
- Use
ITempData.Getfor one-time messages until[SupplyParameterFromTempData]clears after reading. - Multi-targeting libraries: treat
RZ10012as an error. - Go through the antiforgery and QuickGrid items in the ASP.NET Core 11 breaking changes post, and the platform-wide changes in upgrading to .NET 11: breaking changes.
Related: What’s new in ASP.NET Core in .NET 11 (Learn) · Breaking changes in ASP.NET Core 11 (Learn) · Blazor server-side state management: TempData and session (Learn) · ASP.NET Core 11 and OpenAPI 3.2 · all ASP.NET Core tutorials