Skip to content

Fix Blazor WASM page is instantiated twice when navigating between pages with different layouts issue - #70018

Open
BharatRamsf3693 wants to merge 1 commit into
dotnet:mainfrom
BharatRamsf3693:bug-68004-blazor-wasm-page-instantiated-twice
Open

BharatRamsf3693 wants to merge 1 commit into
dotnet:mainfrom
BharatRamsf3693:bug-68004-blazor-wasm-page-instantiated-twice

Conversation

@BharatRamsf3693

Copy link
Copy Markdown
Contributor

Fix Blazor WASM page is instantiated twice when navigating between pages with different layouts issue

Summary of the changes

Prevent duplicate page instantiation during layout transitions

Description

Fixes a Blazor RouteView issue where navigating between pages that use different layouts could instantiate the destination page twice.

Changes made

  • Capture the current RouteData before rendering the layout and page.
  • Use the captured route data when rendering the page body, preventing an old layout from using route data from a newer navigation.
  • Add regression coverage for layout transitions.
  • Verify that the destination page is instantiated only once during navigation.

Fixes #68004

@github-actions github-actions Bot added the area-blazor Includes: Blazor, Razor Components label Oct 23, 2026
@dotnet-policy-service dotnet-policy-service Bot added the community-contribution Indicates that the PR has been added by a community member label Oct 23, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Thanks for your PR, @BharatRamsf3693. Someone from the team will get assigned to your PR shortly and we'll get it reviewed.

@BharatRamsf3693
BharatRamsf3693 marked this pull request as ready for review October 26, 2026 11:17
@BharatRamsf3693
BharatRamsf3693 requested a review from a team as a code owner October 26, 2026 11:17
Copilot AI balanced review requested due to automatic review settings October 26, 2026 11:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The lifecycle-sensitive fix needs human review and confirmation that the unit and browser regressions fail without the fix and pass with it.

No open issues 🎉

What changed in this PR

This PR addresses #68004 by capturing route data for each RouteView render, preventing an outgoing layout from rendering the destination page with newer navigation data.

Changes:

  • Bind page-rendering fragments to the route data captured when their layout is rendered.
  • Add unit coverage for page instantiation, route parameters, and outgoing-page disposal.
  • Add a browser regression scenario with different layouts and lifecycle tracing.
File Description
src/​Components/​test/​testassets/​BasicTestApp/​RouteViewLayoutTransition/​TrackedPageBase.cs Records page construction and lifecycle events.
src/​Components/​test/​testassets/​BasicTestApp/​RouteViewLayoutTransition/​RouteViewLayoutTransitionApp.razor Hosts the router and layout-transition scenario.
src/​Components/​test/​testassets/​BasicTestApp/​RouteViewLayoutTransition/​RootLayout.razor Provides navigation links and lifecycle output.
src/​Components/​test/​testassets/​BasicTestApp/​RouteViewLayoutTransition/​NestedLayout.razor Adds the nested layout used by the initial page.
src/​Components/​test/​testassets/​BasicTestApp/​RouteViewLayoutTransition/​DefaultLayoutPage.razor Supplies the initial page and route parameter display.
src/​Components/​test/​testassets/​BasicTestApp/​RouteViewLayoutTransition/​ComponentLifecycleTrace.cs Stores lifecycle entries and notifies subscribers.
src/​Components/​test/​testassets/​BasicTestApp/​RouteViewLayoutTransition/​ComponentLifecycleLog.razor Displays lifecycle entries in the browser.
src/​Components/​test/​testassets/​BasicTestApp/​RouteViewLayoutTransition/​CascadingStateProvider.razor Supplies cascading state for the regression scenario.
src/​Components/​test/​testassets/​BasicTestApp/​RouteViewLayoutTransition/​AlternateLayoutPage.razor Supplies the destination page with a different layout.
src/​Components/​test/​testassets/​BasicTestApp/​Index.razor Registers the new test scenario.
src/​Components/​test/​E2ETest/​Tests/​RoutingTest.cs Checks destination-page construction count during browser navigation.
src/​Components/​Components/​test/​RouteViewTest.cs Adds renderer-level layout-transition regression coverage.
src/​Components/​Components/​src/​RouteView.cs Captures route data in the page-rendering fragment.

Review effort: Balanced

@PureWeen

PureWeen commented Nov 1, 2026

Copy link
Copy Markdown
Member

/review

@PureWeen PureWeen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two issues need attention at this commit.

Human-directed and GitHub Copilot-generated.

builder.OpenComponent<LayoutView>(0);
builder.AddComponentParameter(1, nameof(LayoutView.Layout), pageLayoutType);
builder.AddComponentParameter(2, nameof(LayoutView.ChildContent), (RenderFragment)RenderPageWithParameters);
builder.AddComponentParameter(2, nameof(LayoutView.ChildContent), (RenderFragment)(contentBuilder => RenderPageWithParameters(contentBuilder, routeData)));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capturing routeData here fixes the different-layout case, but it also lets a superseded fragment replay stale parameters into a retained page. With the same page/layout under a changing non-fixed cascade, the retained layout is queued before RouteView, so this old closure runs first. For reference-valued route parameters, conservative parameter comparison calls SetParametersAsync with the old model and then the new one, allowing stale OnParametersSet{Async} work to race the destination update. Please suppress or version superseded fragments while preserving the different-layout fix, and add a same-page/same-layout reference-parameter regression.

Browser.Equal("second", () => app.FindElement(By.Id("page-value")).Text);
var trace = app.FindElement(By.Id("instantiation-log")).Text;

Assert.Equal(1, trace.Split('\n').Count(line => line.Contains($"{nameof(AlternateLayoutPage)}[") && line.Contains("constructor")));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assertion reads a process-static trace that is never cleared. In the inherited ServerRoutingTest, assembly-level retry reruns the test against the same in-process server fixture; navigating through about:blank resets the browser/circuit, not InstantiationTrace. If an attempt constructs AlternateLayoutPage and then fails transiently, the retry adds another constructor entry and this exactly-one assertion deterministically fails again. Please make the trace attempt- or mount-scoped, or reset it safely before asserting the count.

This branch was previously deployed

1 inactive deployment
copilot-pat-pool — d6e3d78b Deployed Oct 23, 2026 by dotnet-policy-service[bot] via pat_pool #1958
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent-reviewed-needs-changes area-blazor Includes: Blazor, Razor Components community-contribution Indicates that the PR has been added by a community member

3 participants