Goal/constraint is to have a SPA, everything in wasm (even router) but at the same time support pages on the server. Greatest flexibility.
Here I accomplished rendering rules by page locations:
- If page is on client only, it will render InteractiveWebAssemblyRenderMode without prerender.
- If page is on server only, it will render InteractiveServerRenderMode with prerender.
- If page is on server and client, it will render InteractiveWebAssemblyRenderMode with prerender.
Pages on server that are also on client will be added as a link from the client.
Components on server:
Program.cs:
...app.MapRazorComponents<App>() .AddInteractiveServerRenderMode() .AddInteractiveWebAssemblyRenderMode();app.Use404FallbackToClient();app.Run();ApplicationBuilderExtensions.cs:
public static void Use404FallbackToClient(this IApplicationBuilder builder){ builder.Use(async (context, next) => { await next(); // Check if the response status code is 404 and the request path doesn't start with "/_framework" if (context.Response.StatusCode == StatusCodes.Status404NotFound && !context.Request.Path.StartsWithSegments("/_framework")) { // Create a new endpoint for the Razor component rendering var endpointFeature = context.Features.Get<IEndpointFeature>(); if (endpointFeature != null) { endpointFeature.Endpoint = new RouteEndpoint( async httpContext => { context.Response.StatusCode = StatusCodes.Status200OK; // Render the Blazor component (in this case, App or your custom component) var result = new RazorComponentResult<App>(); await result.ExecuteAsync(httpContext); }, RoutePatternFactory.Parse("/404"), 0, new EndpointMetadataCollection(new[] { new ComponentTypeMetadata(typeof(_404)) }),"404 Endpoint" ); } // Render the Razor component directly await endpointFeature.Endpoint.RequestDelegate(context); } });}App.razor:
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><base href="/" /><link rel="stylesheet" href="css/bootstrap.min.css" /><link rel="stylesheet" href="css/app.css" /><link rel="stylesheet" href="Sindikat.Web.styles.css" /><link rel="icon" type="image/png" href="favicon.png" /><HeadOutlet @rendermode="RenderModeForPage" /><RadzenTheme Theme="material" @rendermode="RenderModeForPage" /></head><body> @if (ServerOnlyPage) {<Routes @rendermode="RenderModeForPage" /> } else {<Sindikat.Web.Client.Components.Routes @rendermode="RenderModeForPage" /> }<script src="_framework/blazor.web.js"></script><script src="_content/Radzen.Blazor/Radzen.Blazor.js?v=@(typeof(Radzen.Colors).Assembly.GetName().Version)"></script><script src="js/sindikatAppBundle.js"></script></body></html> public partial class App { private static Assembly serverAssembly; private static Assembly clientAssembly; static App() { serverAssembly = typeof(Program).Assembly; clientAssembly = typeof(Sindikat.Web.Client.ModuleDefinitions.Module).Assembly; } [CascadingParameter] private HttpContext HttpContext { get; set; } = default!; private IComponentRenderMode? RenderModeForPage { get; set; } private bool ServerOnlyPage; protected override void OnInitialized() { var endpointFeature = HttpContext.Features.Get<IEndpointFeature>(); var type = endpointFeature.Endpoint?.Metadata.GetMetadata<ComponentTypeMetadata>()?.Type; var hasServerPage = type?.Assembly == serverAssembly; var hasClientPage = clientAssembly.GetType($"Sindikat.Web.Client.Components.Pages.{type.Name}") != null; ServerOnlyPage = hasServerPage && !hasClientPage; var renderMode = type?.GetRenderMode(); if (renderMode != null) RenderModeForPage = renderMode; else RenderModeForPage = ServerOnlyPage ? new InteractiveServerRenderMode() : new InteractiveWebAssemblyRenderMode(hasServerPage); } }Routes.razor:
@using Microsoft.AspNetCore.Components.WebAssembly.Hosting @using Sindikat.Web.Client.Components<Router AppAssembly="typeof(Program).Assembly"><Found Context="routeData"><RouteFound RouteData="routeData" /></Found></Router>Components on Client:
Home.razor:
I decided that all pages will only have a component that will have page content.
@page "/"<Sindikat.Web.Client.Components.Home></Sindikat.Web.Client.Components.Home>Routes.razor:
@using Microsoft.AspNetCore.Components.WebAssembly.Hosting<Router AppAssembly="typeof(Program).Assembly"><Found Context="routeData"><RouteFound RouteData="routeData" /></Found></Router>404.razor:
@page "/404"