Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/Bit.CI.Release.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@
<Folder Name="/Brouter/">
<Project Path="Brouter/Bit.Brouter/Bit.Brouter.csproj" />
</Folder>
<Folder Name="/Brouter/Demo/">
<Project Path="Brouter/Demo/Bit.Brouter.Demo.Core/Bit.Brouter.Demo.Core.csproj" />
<Project Path="Brouter/Demo/Bit.Brouter.Demo.Web/Bit.Brouter.Demo.Web.csproj" />
<Folder Name="/Brouter/InteralTests/Demo/">
<Project Path="Brouter/InteralTests/Demo/Bit.Brouter.Demo.Core/Bit.Brouter.Demo.Core.csproj" />
<Project Path="Brouter/InteralTests/Demo/Bit.Brouter.Demo.Web/Bit.Brouter.Demo.Web.csproj" />
</Folder>
<Folder Name="/Brouter/Tests/">
<Project Path="Brouter/Tests/Bit.Brouter.Tests/Bit.Brouter.Tests.csproj" />
Expand Down
8 changes: 4 additions & 4 deletions src/Bit.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,12 @@
<File Path="Brouter/README.md" />
<Project Path="Brouter/Bit.Brouter/Bit.Brouter.csproj" />
</Folder>
<Folder Name="/Brouter/Demo/">
<Project Path="Brouter/Demo/Bit.Brouter.Demo.Core/Bit.Brouter.Demo.Core.csproj" />
<Project Path="Brouter/Demo/Bit.Brouter.Demo.Maui/Bit.Brouter.Demo.Maui.csproj">
<Folder Name="/Brouter/InteralTests/Demo/">
<Project Path="Brouter/InteralTests/Demo/Bit.Brouter.Demo.Core/Bit.Brouter.Demo.Core.csproj" />
<Project Path="Brouter/InteralTests/Demo/Bit.Brouter.Demo.Maui/Bit.Brouter.Demo.Maui.csproj">
<Deploy />
</Project>
<Project Path="Brouter/Demo/Bit.Brouter.Demo.Web/Bit.Brouter.Demo.Web.csproj" />
<Project Path="Brouter/InteralTests/Demo/Bit.Brouter.Demo.Web/Bit.Brouter.Demo.Web.csproj" />
</Folder>
<Folder Name="/Brouter/Tests/">
<Project Path="Brouter/Tests/Bit.Brouter.Tests/Bit.Brouter.Tests.csproj" />
Expand Down
10 changes: 0 additions & 10 deletions src/Brouter/Bit.Brouter.Web.slnf

This file was deleted.

19 changes: 13 additions & 6 deletions src/Brouter/Bit.Brouter.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,19 @@
<File Path="../global.json" />
<File Path="README.md" />
</Folder>
<Folder Name="/Demo/">
<Project Path="Demo/Bit.Brouter.Demo.Core/Bit.Brouter.Demo.Core.csproj" />
<Project Path="Demo/Bit.Brouter.Demo.Maui/Bit.Brouter.Demo.Maui.csproj">
<Deploy />
</Project>
<Project Path="Demo/Bit.Brouter.Demo.Web/Bit.Brouter.Demo.Web.csproj" />
<Folder Name="/InteralDemos/">
<Project Path="InteralDemos/Core/Bit.Brouter.Demos.Core.csproj" />
</Folder>
<Folder Name="/InteralDemos/Auto/">
<Project Path="InteralDemos/Auto/Bit.Brouter.Demos.Auto.Client/Bit.Brouter.Demos.Auto.Client.csproj" />
<Project Path="InteralDemos/Auto/Bit.Brouter.Demos.Auto/Bit.Brouter.Demos.Auto.csproj" />
</Folder>
<Folder Name="/InteralDemos/Server/">
<Project Path="InteralDemos/Server/Bit.Brouter.Demos.Server.csproj" />
</Folder>
<Folder Name="/InteralDemos/Wasm/">
<Project Path="InteralDemos/Wasm/Bit.Brouter.Demos.Wasm.Client/Bit.Brouter.Demos.Wasm.Client.csproj" />
<Project Path="InteralDemos/Wasm/Bit.Brouter.Demos.Wasm/Bit.Brouter.Demos.Wasm.csproj" />
</Folder>
<Folder Name="/Tests/">
<Project Path="Tests/Bit.Brouter.Tests/Bit.Brouter.Tests.csproj" />
Expand Down
59 changes: 50 additions & 9 deletions src/Brouter/Bit.Brouter/Brouter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,18 +118,45 @@ protected override void OnInitialized()
CurrentLocation = ComputeLocation();
}

protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();

// Yield once so ComponentBase performs the initial synchronous render of our
// ChildContent. That first render is what causes the declared <BrouterRoute> children to
// register themselves with us (each one calls RegisterRoute from its own OnInitialized).
// Until they've registered there is nothing to match against, which is why the initial
// match cannot run any earlier than this.
//
// Doing the initial match here - rather than in OnAfterRenderAsync - is what enables
// static server prerendering. OnAfterRenderAsync never runs during prerender, so the old
// placement left the prerendered HTML empty (no route was ever matched server-side).
// OnInitializedAsync, by contrast, runs during prerender and the renderer awaits it - and
// the StateHasChanged it triggers - before serializing the HTML, so the matched route is
// included in the prerendered output. When the component later becomes interactive its
// lifecycle runs again and the match re-runs naturally.
await Task.Yield();

// Initial render: the From is Empty (we just mounted), the To is the URL we're at now.
await ProcessNavigationAsync(BrouterLocation.Empty, CurrentLocation);
}
Comment thread
msynk marked this conversation as resolved.

protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);

if (firstRender is false) return;

// Enabling navigation interception is best-effort: under prerender, on a disconnected
// circuit, or on an interop failure it can throw, but the navigation pipeline itself
// (and any subsequent reconnects / interactivity handoff) does not depend on it
// succeeding right now. Mirror the defensive style used in BrouterLink and
// BrouterService.BackAsync so a transient failure here can't kill the whole first
// navigation. Once the circuit/runtime is fully ready, Blazor will retry interception
// Enabling navigation interception genuinely requires an interactive runtime, so it stays
// in OnAfterRenderAsync, which only runs once interactivity is established. Under prerender
// this method doesn't run at all - that's fine: the initial match already happened in
// OnInitializedAsync, and interception is enabled here once the component goes interactive.
//
// Enabling navigation interception is best-effort: on a disconnected circuit or an interop
// failure it can throw, but the navigation pipeline itself (and any subsequent reconnects /
// interactivity handoff) does not depend on it succeeding right now. Mirror the defensive
// style used in BrouterLink and BrouterService.BackAsync so a transient failure here can't
// kill navigation. Once the circuit/runtime is fully ready, Blazor will retry interception
// attachment naturally on the next user click via NavigationManager fallback paths.
try
{
Expand All @@ -139,9 +166,6 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
catch (JSException) { /* JS interop failure; non-fatal */ }
catch (InvalidOperationException) { /* interop unavailable during prerender */ }
catch (TaskCanceledException) { /* component disposed mid-call */ }

// Initial render: the From is Empty (we just mounted), the To is the URL we're at now.
await ProcessNavigationAsync(BrouterLocation.Empty, CurrentLocation);
}

protected override void BuildRenderTree(RenderTreeBuilder builder)
Expand Down Expand Up @@ -551,6 +575,16 @@ private async ValueTask ProcessNavigationAsync(BrouterLocation from, BrouterLoca
{
return;
}
catch (NavigationException)
{
// During static server rendering / prerender, NavigationManager.NavigateTo
// throws NavigationException as the framework's redirect signal (a loader may
// redirect, e.g. an auth gate). It must unwind out of OnInitializedAsync so the
// endpoint can issue the HTTP redirect; swallowing it into OnError would drop
// the redirect entirely. Interactive NavigateTo never throws, so this is inert
// outside SSR.
throw;
}
catch (Exception ex)
{
await service.InvokeOnError(ctx, ex);
Expand Down Expand Up @@ -583,6 +617,13 @@ private async ValueTask ProcessNavigationAsync(BrouterLocation from, BrouterLoca
{
// navigation was superseded; nothing to do
}
catch (NavigationException)
{
// SSR/prerender redirect signal (see the loader catch above). Let it propagate out of
// OnInitializedAsync so the framework can turn it into an HTTP redirect. A guard or
// OnNavigating handler that redirects via NavigationManager during prerender lands here.
throw;
}
catch (Exception ex)
{
await service.InvokeOnError(ctx, ex);
Expand Down
17 changes: 0 additions & 17 deletions src/Brouter/Demo/Bit.Brouter.Demo.Maui/App.xaml

This file was deleted.

11 changes: 0 additions & 11 deletions src/Brouter/Demo/Bit.Brouter.Demo.Maui/App.xaml.cs

This file was deleted.

This file was deleted.

This file was deleted.

15 changes: 0 additions & 15 deletions src/Brouter/Demo/Bit.Brouter.Demo.Maui/MainPage.xaml

This file was deleted.

9 changes: 0 additions & 9 deletions src/Brouter/Demo/Bit.Brouter.Demo.Maui/MainPage.xaml.cs

This file was deleted.

22 changes: 0 additions & 22 deletions src/Brouter/Demo/Bit.Brouter.Demo.Maui/MauiProgram.cs

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Loading