You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在同一Blazor进程中实现用户端静态SSR与/admin/*路径InteractiveServer渲染模式的共存?

如何在同一Blazor进程中实现用户端静态SSR与/admin/*路径InteractiveServer渲染模式的共存?

这个需求确实太常见了!我之前做项目的时候也卡过这个点,折腾了好几种方案,最终找到了两个比较靠谱的思路,你可以根据自己的项目规模选:


方案一:单项目单路由,拆分布局交互式组件(解决你Attempt1的布局痛点)

核心思路:布局本身不能加@rendermode,但我们可以把布局里需要交互式的部分(比如MudBlazor的抽屉、导航栏)拆成独立的交互式组件,给这个组件加上InteractiveServer渲染模式,再用它包裹@Body。这样既不用给布局加渲染模式,又能让布局里的交互式组件正常工作。

详细步骤:

  1. 路由区分布局
    Routes.razor里根据路由前缀判断,给/admin路径的页面自动分配AdminLayout:

    <Router AppAssembly="@typeof(App).Assembly">
        <Found Context="routeData">
            @if (routeData.Path.StartsWith("/admin", StringComparison.OrdinalIgnoreCase))
            {
                <!-- 给admin页面默认设置InteractiveServer渲染模式 -->
                <RouteView 
                    RouteData="@routeData" 
                    DefaultLayout="@typeof(AdminLayout)" 
                    DefaultRenderMode="@RenderMode.InteractiveServer" />
            }
            else
            {
                <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
            }
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>抱歉,页面不存在</p>
            </LayoutView>
        </NotFound>
    </Router>
    
  2. 拆分布局的交互式部分
    比如你有个MudBlazor的抽屉包裹@Body,把这部分拆成AdminLayoutInteractiveWrapper.razor

    @rendermode InteractiveServer
    
    <!-- 这里放你需要交互式的MudBlazor组件,比如抽屉、导航栏 -->
    <MudDrawer @bind-Open="_isDrawerOpen">
        <MudDrawerHeader>Admin 控制台</MudDrawerHeader>
        <MudNavMenu>
            <MudNavLink Href="/admin/dashboard">仪表盘</MudNavLink>
        </MudNavMenu>
    </MudDrawer>
    
    <MudMainContent>
        <!-- 用ChildContent接收布局的@Body -->
        @ChildContent
    </MudMainContent>
    
    @code {
        [Parameter] public RenderFragment ChildContent { get; set; }
        private bool _isDrawerOpen = true;
    
        // 这里可以加交互式逻辑,比如点击按钮切换抽屉
        public void ToggleDrawer() => _isDrawerOpen = !_isDrawerOpen;
    }
    
  3. 修改AdminLayout引用Wrapper
    @Body放到上面的交互式Wrapper里,并且只在AdminLayout里加载blazor.web.js

    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Admin 控制台</title>
        <link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
    </head>
    <body>
        <!-- 用交互式Wrapper包裹@Body -->
        <AdminLayoutInteractiveWrapper>
            @Body
        </AdminLayoutInteractiveWrapper>
    
        <!-- 只在admin布局加载Blazor交互式脚本 -->
        <script src="_framework/blazor.web.js"></script>
        <script src="_content/MudBlazor/MudBlazor.min.js"></script>
    </body>
    </html>
    
  4. 处理导航污染问题
    从admin页面跳转到首页时,给a标签加上data-enhance-nav="false",避免Blazor增强导航污染静态SSR页面:

    <a href="/" data-enhance-nav="false">回到首页</a>
    

    从首页跳转到admin时用普通a标签就行,因为首页没有加载Blazor脚本,不会触发增强导航。


方案二:单项目双根组件,路由完全隔离(解决你Attempt2的资源问题)

如果你的admin功能比较复杂,想和主站点完全隔离,可以在单项目里创建两个根组件(App.razor对应静态SSR,AdminApp.razor对应交互式Server),分别映射路由前缀,这样布局可以直接用交互式组件,不需要拆分。

详细步骤:

  1. 创建Admin根组件
    项目里新增AdminApp.razor(admin的入口)和AdminRoutes.razor(admin的路由):

    <!-- AdminApp.razor -->
    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Admin 控制台</title>
        <link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
        <!-- 引用主项目的静态资源,比如importmap -->
        <script type="importmap" src="/importmap.json"></script>
    </head>
    <body>
        <AdminRoutes />
        <script src="_framework/blazor.web.js"></script>
        <script src="_content/MudBlazor/MudBlazor.min.js"></script>
    </body>
    </html>
    
    <!-- AdminRoutes.razor -->
    @rendermode InteractiveServer
    
    <Router AppAssembly="@typeof(AdminApp).Assembly">
        <Found Context="routeData">
            <RouteView RouteData="@routeData" DefaultLayout="@typeof(AdminLayout)" />
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(AdminLayout)">
                <p>Admin页面不存在</p>
            </LayoutView>
        </NotFound>
    </Router>
    
  2. Program.cs里映射两个根组件
    给admin的根组件指定路由前缀/admin,并启用InteractiveServer模式:

    // 映射静态SSR主站点,根路径  
    app.MapRazorComponents<App>()
       .AddStaticRenderMode();
    
    // 映射admin交互式站点,路由前缀/admin
    app.MapRazorComponents<AdminApp>()
       .AddInteractiveServerRenderMode()
       .MapWhen(ctx => ctx.Request.Path.StartsWithSegments("/admin"));
    
  3. AdminLayout直接用交互式组件
    因为AdminApp已经启用了InteractiveServer模式,AdminRoutes也加了@rendermode,所以AdminLayout里可以直接用交互式组件包裹@Body,不需要拆分:

    <!-- AdminLayout.razor -->
    <MudDrawer Open="@_drawerOpen">
        <!-- 交互式导航栏 -->
    </MudDrawer>
    <MudMainContent>
        @Body
    </MudMainContent>
    
    @code {
        private bool _drawerOpen = true;
    
        // 直接写交互式逻辑,比如点击事件
        void ToggleDrawer() => _drawerOpen = !_drawerOpen;
    }
    
  4. 解决资源加载问题
    因为两个根组件在同一个项目里,静态资源是共享的,只要引用路径正确(比如用绝对路径/importmap.json),ResourcePreloader和ImportMap就能正常工作。如果有admin专属的静态资源,可以放在wwwroot/admin目录下,引用时用/admin/xxx


方案对比与选择

方案优点缺点适用场景
方案一结构简单,无需额外根组件,逻辑集中布局交互式部分需要拆组件小型项目,admin功能简单
方案二路由完全隔离,admin逻辑独立,布局无需拆分需要维护两个根组件,资源路径要注意大型项目,admin功能复杂

最后补充几个注意点

  1. 无论用哪个方案,主站点的布局绝对不能加载blazor.web.js,只有admin的布局/根组件里加载。
  2. 交互式组件的@rendermode:方案一里可以通过RouteViewDefaultRenderMode给所有admin页面统一设置,不用每个组件单独加;方案二里因为根组件已经启用了InteractiveServer模式,子组件默认继承,也不用重复加。
  3. 测试时要清空浏览器缓存,避免旧的脚本或资源导致的奇怪问题。

我自己做项目时用的是方案一,因为结构简单,拆组件的成本也不高,完全能满足需求。你可以先试试方案一,不行再换方案二~

火山引擎 最新活动